From 36df44f6402bc63573560a0158049a47a15c9b1b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 15 Jul 2025 16:01:02 +0200 Subject: [PATCH 01/13] chore: remove validation logic from AttachmentModal --- src/components/AttachmentModal.tsx | 110 +---------------------------- 1 file changed, 3 insertions(+), 107 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index cb1590b0dffa..4edfce8a4726 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -14,7 +14,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import attachmentModalHandler from '@libs/AttachmentModalHandler'; import fileDownload from '@libs/fileDownload'; -import {cleanFileName, getFileName, validateImageForCorruption} from '@libs/fileDownload/FileUtils'; +import {getFileName} from '@libs/fileDownload/FileUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {hasEReceipt, hasMissingSmartscanFields, hasReceipt, hasReceiptSource, isReceiptBeingScanned} from '@libs/TransactionUtils'; @@ -28,7 +28,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type ModalType from '@src/types/utils/ModalType'; import viewRef from '@src/types/utils/viewRef'; import AttachmentCarousel from './Attachments/AttachmentCarousel'; import AttachmentCarouselPagerContext from './Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; @@ -63,7 +62,6 @@ type ImagePickerResponse = { type FileObject = Partial; type ChildrenProps = { - displayFileInModal: (data: FileObject) => void; show: () => void; }; @@ -202,7 +200,6 @@ function AttachmentModal({ const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(null); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); const [sourceState, setSourceState] = useState(() => source); - const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE); const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false); const [isDownloadButtonReadyToBeShown, setIsDownloadButtonReadyToBeShown] = React.useState(true); const isPDFLoadError = useRef(false); @@ -219,8 +216,6 @@ function AttachmentModal({ const [currentAttachmentLink, setCurrentAttachmentLink] = useState(attachmentLink); const {setAttachmentError, isErrorInAttachment, clearAttachmentErrors} = useAttachmentErrors(); - // TODO: Remove the logic for validating files, ideally in a follow-up to avoid breaking changes/regressions - const [file, setFile] = useState( originalFileName ? { @@ -251,17 +246,6 @@ function AttachmentModal({ [onCarouselAttachmentChange], ); - /** - * If our attachment is a PDF, return the unswipeable Modal type. - */ - const getModalType = useCallback( - (sourceURL: string, fileObject: FileObject) => - sourceURL && (Str.isPDF(sourceURL) || (fileObject && Str.isPDF(fileObject.name ?? translate('attachmentView.unknownFilename')))) - ? CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE - : CONST.MODAL.MODAL_TYPE.CENTERED, - [translate], - ); - const setDownloadButtonVisibility = useCallback( (isButtonVisible: boolean) => { if (isDownloadButtonReadyToBeShown === isButtonVisible) { @@ -326,91 +310,6 @@ function AttachmentModal({ Navigation.goBack(); }, [transaction]); - const isValidFile = useCallback( - (fileObject: FileObject) => - validateImageForCorruption(fileObject) - .then(() => { - if (fileObject.size && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - setIsAttachmentInvalid(true); - setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooLarge'); - setAttachmentInvalidReason('attachmentPicker.sizeExceeded'); - return false; - } - - if (fileObject.size && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { - setIsAttachmentInvalid(true); - setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall'); - setAttachmentInvalidReason('attachmentPicker.sizeNotMet'); - return false; - } - - return true; - }) - .catch(() => { - setIsAttachmentInvalid(true); - setAttachmentInvalidReasonTitle('attachmentPicker.attachmentError'); - setAttachmentInvalidReason('attachmentPicker.errorWhileSelectingCorruptedAttachment'); - return false; - }), - [], - ); - - const isDirectoryCheck = useCallback((data: FileObject) => { - if ('webkitGetAsEntry' in data && (data as DataTransferItem).webkitGetAsEntry()?.isDirectory) { - setIsAttachmentInvalid(true); - setAttachmentInvalidReasonTitle('attachmentPicker.attachmentError'); - setAttachmentInvalidReason('attachmentPicker.folderNotAllowedMessage'); - return false; - } - return true; - }, []); - - const validateAndDisplayFileToUpload = useCallback( - (data: FileObject) => { - if (!data || !isDirectoryCheck(data)) { - return; - } - let fileObject = data; - if ('getAsFile' in data && typeof data.getAsFile === 'function') { - fileObject = data.getAsFile() as FileObject; - } - if (!fileObject) { - return; - } - - isValidFile(fileObject).then((isValid) => { - if (!isValid) { - return; - } - if (fileObject instanceof File) { - /** - * Cleaning file name, done here so that it covers all cases: - * upload, drag and drop, copy-paste - */ - let updatedFile = fileObject; - const cleanName = cleanFileName(updatedFile.name); - if (updatedFile.name !== cleanName) { - updatedFile = new File([updatedFile], cleanName, {type: updatedFile.type}); - } - const inputSource = URL.createObjectURL(updatedFile); - updatedFile.uri = inputSource; - const inputModalType = getModalType(inputSource, updatedFile); - setIsModalOpen(true); - setSourceState(inputSource); - setFile(updatedFile); - setModalType(inputModalType); - } else if (fileObject.uri) { - const inputModalType = getModalType(fileObject.uri, fileObject); - setIsModalOpen(true); - setSourceState(fileObject.uri); - setFile(fileObject); - setModalType(inputModalType); - } - }); - }, - [isValidFile, getModalType, isDirectoryCheck], - ); - /** * Closes the modal. * @param {boolean} [shouldCallDirectly] If true, directly calls `onModalClose`. @@ -520,7 +419,7 @@ function AttachmentModal({ return ( <> { @@ -701,10 +600,7 @@ function AttachmentModal({ /> )} - {children?.({ - displayFileInModal: validateAndDisplayFileToUpload, - show: openModal, - })} + {children?.({show: openModal})} ); } From 82e48cecdfe396a2378cddf39d042fa0d07e2999 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 16 Jul 2025 17:32:48 +0200 Subject: [PATCH 02/13] chore: remove unused component --- src/pages/home/report/ReportDropUI.tsx | 35 -------------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/pages/home/report/ReportDropUI.tsx diff --git a/src/pages/home/report/ReportDropUI.tsx b/src/pages/home/report/ReportDropUI.tsx deleted file mode 100644 index fad58d60bbfa..000000000000 --- a/src/pages/home/report/ReportDropUI.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import DragAndDropConsumer from '@components/DragAndDrop/Consumer'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; - -type ReportDropUIProps = { - /** Callback to execute when a file is dropped. */ - onDrop: (event: DragEvent) => void; -}; -function ReportDropUI({onDrop}: ReportDropUIProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - return ( - - - - - - {translate('reportActionCompose.dropToUpload')} - - - ); -} - -ReportDropUI.displayName = 'ReportDropUI'; - -export default ReportDropUI; From ad3f459af5ec1b8ed85d698f36bb1b09d2b86f3a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 17 Jul 2025 18:26:29 +0200 Subject: [PATCH 03/13] chore: remove function that is not working --- src/components/AttachmentComposerModal.tsx | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/components/AttachmentComposerModal.tsx b/src/components/AttachmentComposerModal.tsx index dace0b604c94..d0e60953f5de 100644 --- a/src/components/AttachmentComposerModal.tsx +++ b/src/components/AttachmentComposerModal.tsx @@ -75,7 +75,6 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide const [page, setPage] = useState(0); const [currentAttachment, setCurrentAttachment] = useState(null); - // TODO: remove unnecessary logic, ideally in a follow-up PR to avoid breaking changes/regressions /** * If our attachment is a PDF, return the unswipeable Modal type. */ @@ -107,20 +106,6 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide setIsModalOpen(false); }, [isModalOpen, onConfirm, validFilesToUpload, currentAttachment]); - const closeConfirmModal = useCallback(() => { - setIsFileErrorModalVisible(false); - }, []); - - // TODO: Check if this function is still needed, as it doesn't work. - const isDirectoryCheck = useCallback((data: FileObject) => { - if ('webkitGetAsEntry' in data && (data as DataTransferItem).webkitGetAsEntry()?.isDirectory) { - setFileError(CONST.FILE_VALIDATION_ERRORS.FOLDER_NOT_ALLOWED); - setIsFileErrorModalVisible(true); - return false; - } - return true; - }, []); - /** * Sanitizes file names and ensures proper URI references for file system compatibility */ @@ -203,12 +188,12 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide }) .filter((fileObject): fileObject is FileObject => fileObject !== null); - if (!fileObjects.length || fileObjects.some((fileObject) => !isDirectoryCheck(fileObject))) { + if (!fileObjects.length) { return; } validateFiles(fileObjects); }, - [cleanFileObjectName, isDirectoryCheck, validateFiles], + [cleanFileObjectName, validateFiles], ); const closeModal = useCallback(() => { @@ -216,13 +201,12 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide }, []); const closeAndResetModal = useCallback(() => { - closeConfirmModal(); closeModal(); InteractionManager.runAfterInteractions(() => { setFileError(null); setValidFilesToUpload([]); }); - }, [closeConfirmModal, closeModal]); + }, [closeModal]); const openModal = useCallback(() => { setIsModalOpen(true); From ccad5e446db43e7e0730b45a728048c9baccddc8 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 17 Jul 2025 19:47:41 +0200 Subject: [PATCH 04/13] chore: fix folder validation, remove unused code --- src/components/AttachmentComposerModal.tsx | 53 ++++++------------- src/hooks/useFilesValidation.tsx | 26 ++++++--- src/pages/Search/SearchPage.tsx | 2 +- .../ReportActionCompose.tsx | 8 +-- .../step/IOURequestStepConfirmation.tsx | 2 +- .../request/step/IOURequestStepScan/index.tsx | 2 +- 6 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/components/AttachmentComposerModal.tsx b/src/components/AttachmentComposerModal.tsx index d0e60953f5de..0bc961ffb6b6 100644 --- a/src/components/AttachmentComposerModal.tsx +++ b/src/components/AttachmentComposerModal.tsx @@ -35,7 +35,7 @@ type ImagePickerResponse = { type FileObject = Partial; type ChildrenProps = { - displayFilesInModal: (data: FileObject[]) => void; + displayFilesInModal: (data: FileObject[], items?: DataTransferItem[]) => void; show: () => void; }; @@ -67,8 +67,6 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide const {shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows} = useCarouselArrows(); const [isModalOpen, setIsModalOpen] = useState(false); - const [fileError, setFileError] = useState | null>(null); - const [isFileErrorModalVisible, setIsFileErrorModalVisible] = useState(false); const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE); const [validFilesToUpload, setValidFilesToUpload] = useState([]); const [attachments, setAttachments] = useState([]); @@ -139,7 +137,7 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide return; } - if (validFilesToUpload.length > 0 && !fileError) { + if (validFilesToUpload.length > 0) { // Convert all files to attachments const newAttachments = validFilesToUpload.map((fileObject) => { const source = fileObject.uri ?? ''; @@ -158,40 +156,39 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide setIsModalOpen(true); } - }, [fileError, validFilesToUpload, convertFileToAttachment, getModalType]); + }, [validFilesToUpload, convertFileToAttachment, getModalType]); const {ErrorModal, validateFiles, PDFValidationComponent} = useFilesValidation(setValidFilesToUpload, false); - const confirmAndContinue = () => { - if (fileError === CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED) { - validateFiles(validFilesToUpload); - } - setIsFileErrorModalVisible(false); - InteractionManager.runAfterInteractions(() => { - setFileError(null); - }); - }; - const validateAndDisplayMultipleFilesToUpload = useCallback( - (data: FileObject[]) => { + (data: FileObject[], items?: DataTransferItem[]) => { if (!data?.length) { return; } + const validIndices: number[] = []; const fileObjects = data - .map((item) => { + .map((item, index) => { let fileObject = item; if ('getAsFile' in item && typeof item.getAsFile === 'function') { fileObject = item.getAsFile() as FileObject; } - return cleanFileObjectName(fileObject); + const cleanedFileObject = cleanFileObjectName(fileObject); + if (cleanedFileObject !== null) { + validIndices.push(index); + } + return cleanedFileObject; }) .filter((fileObject): fileObject is FileObject => fileObject !== null); if (!fileObjects.length) { return; } - validateFiles(fileObjects); + + // Create a filtered items array that matches the fileObjects + const filteredItems = items && validIndices.length > 0 ? validIndices.map((index) => items.at(index) ?? ({} as DataTransferItem)) : undefined; + + validateFiles(fileObjects, filteredItems); }, [cleanFileObjectName, validateFiles], ); @@ -200,14 +197,6 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide setIsModalOpen(false); }, []); - const closeAndResetModal = useCallback(() => { - closeModal(); - InteractionManager.runAfterInteractions(() => { - setFileError(null); - setValidFilesToUpload([]); - }); - }, [closeModal]); - const openModal = useCallback(() => { setIsModalOpen(true); }, []); @@ -296,16 +285,6 @@ function AttachmentComposerModal({onConfirm, onModalShow = () => {}, onModalHide - {children?.({ displayFilesInModal: validateAndDisplayMultipleFilesToUpload, show: openModal, diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx index 9411a73684b9..2948f3128bd8 100644 --- a/src/hooks/useFilesValidation.tsx +++ b/src/hooks/useFilesValidation.tsx @@ -43,6 +43,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi const validatedPDFs = useRef([]); const validFiles = useRef([]); const filesToValidate = useRef([]); + const dataTransferItemList = useRef([]); const collectedErrors = useRef([]); const deduplicateErrors = useCallback((errors: ErrorObject[]) => { @@ -70,6 +71,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi validatedPDFs.current = []; validFiles.current = []; filesToValidate.current = []; + dataTransferItemList.current = []; collectedErrors.current = []; }, [setIsLoaderVisible]); @@ -85,7 +87,16 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsErrorModalVisible(true); }; - const isValidFile = (originalFile: FileObject, isCheckingMultipleFiles?: boolean) => { + const isValidFile = (originalFile: FileObject, item: DataTransferItem | undefined, isCheckingMultipleFiles?: boolean) => { + if (item && item.kind === 'file' && 'webkitGetAsEntry' in item) { + const entry = item.webkitGetAsEntry(); + + if (entry?.isDirectory) { + collectedErrors.current.push({error: CONST.FILE_VALIDATION_ERRORS.FOLDER_NOT_ALLOWED}); + return Promise.resolve(false); + } + } + return normalizeFileObject(originalFile) .then((normalizedFile) => validateImageForCorruption(normalizedFile).then(() => { @@ -149,7 +160,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi } }, [deduplicateErrors, pdfFilesToRender.length, proceedWithFilesAction, resetValidationState]); - const validateAndResizeFiles = (files: FileObject[]) => { + const validateAndResizeFiles = (files: FileObject[], items: DataTransferItem[]) => { // Early return for empty files if (files.length === 0) { return; @@ -158,7 +169,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi // Reset collected errors for new validation collectedErrors.current = []; - Promise.all(files.map((file) => isValidFile(file, files.length > 1).then((isValid) => (isValid ? file : null)))) + Promise.all(files.map((file, index) => isValidFile(file, items.at(index), files.length > 1).then((isValid) => (isValid ? file : null)))) .then((validationResults) => { const filteredResults = validationResults.filter((result): result is FileObject => result !== null); const pdfsToLoad = filteredResults.filter((file) => Str.isPDF(file.name ?? '')); @@ -224,22 +235,25 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi }); }; - const validateFiles = (files: FileObject[]) => { + const validateFiles = (files: FileObject[], items?: DataTransferItem[]) => { if (files.length > 1) { setIsValidatingMultipleFiles(true); } if (files.length > CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT) { filesToValidate.current = files.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT); + if (items) { + dataTransferItemList.current = items.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT) + } setErrorAndOpenModal(CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED); } else { - validateAndResizeFiles(files); + validateAndResizeFiles(files, items ?? []); } }; const onConfirm = () => { if (fileError === CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED) { setIsErrorModalVisible(false); - validateAndResizeFiles(filesToValidate.current); + validateAndResizeFiles(filesToValidate.current, dataTransferItemList.current); return; } diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index a78ed7089ec2..6ed365d6fd12 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -442,7 +442,7 @@ function SearchPage({route}: SearchPageProps) { file.uri = URL.createObjectURL(file); }); - validateFiles(files); + validateFiles(files, Array.from(e.dataTransfer?.items ?? [])); }; const createExportAll = useCallback(() => { diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 3110b6fbd055..b82c0b17b01f 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -531,7 +531,7 @@ function ReportActionCompose({ const file = e?.dataTransfer?.files?.[0]; if (file) { file.uri = URL.createObjectURL(file); - validateFiles([file]); + validateFiles([file], Array.from(e.dataTransfer?.items ?? [])); return; } } @@ -545,7 +545,7 @@ function ReportActionCompose({ file.uri = URL.createObjectURL(file); }); - validateFiles(files); + validateFiles(files, Array.from(e.dataTransfer?.items ?? [])); }; return ( @@ -592,14 +592,14 @@ function ReportActionCompose({ file.uri = URL.createObjectURL(file); return file; }); - displayFilesInModal(files); + displayFilesInModal(files, Array.from(event.dataTransfer?.items ?? [])); return; } const data = event.dataTransfer?.files[0]; if (data) { data.uri = URL.createObjectURL(data); - displayFilesInModal([data]); + displayFilesInModal([data], Array.from(event.dataTransfer?.items ?? [])); } }; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 6318d82c7107..8ac9942769a5 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -966,7 +966,7 @@ function IOURequestStepConfirmation({ const file = e?.dataTransfer?.files[0]; if (file) { file.uri = URL.createObjectURL(file); - validateFiles([file]); + validateFiles([file], e.dataTransfer?.items); } }; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 8f0cb77b2486..b86b26c23a98 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -629,7 +629,7 @@ function IOURequestStepScan({ file.uri = URL.createObjectURL(file); }); - validateFiles(files); + validateFiles(files, Array.from(e.dataTransfer?.items ?? [])); }; /** From 2fe62b0a7c24b71d48b555f217a91fd7c44d9f11 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Sat, 19 Jul 2025 12:30:14 +0200 Subject: [PATCH 05/13] feat: files ordering --- src/hooks/useFilesValidation.tsx | 40 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx index 2948f3128bd8..deb49a6ea73f 100644 --- a/src/hooks/useFilesValidation.tsx +++ b/src/hooks/useFilesValidation.tsx @@ -45,6 +45,14 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi const filesToValidate = useRef([]); const dataTransferItemList = useRef([]); const collectedErrors = useRef([]); + const originalFileOrder = useRef>(new Map()); + + const updateFileOrderMapping = useCallback((oldFile: FileObject | undefined, newFile: FileObject) => { + const originalIndex = originalFileOrder.current.get(oldFile?.uri ?? ''); + if (originalIndex !== undefined) { + originalFileOrder.current.set(newFile.uri ?? '', originalIndex); + } + }, []); const deduplicateErrors = useCallback((errors: ErrorObject[]) => { const uniqueErrors = new Set(); @@ -73,6 +81,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi filesToValidate.current = []; dataTransferItemList.current = []; collectedErrors.current = []; + originalFileOrder.current.clear(); }, [setIsLoaderVisible]); const hideModalAndReset = useCallback(() => { @@ -155,7 +164,8 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsErrorModalVisible(true); } } else if (validFiles.current.length > 0) { - proceedWithFilesAction(validFiles.current); + const sortedFiles = validFiles.current.sort((a, b) => (originalFileOrder.current.get(a.uri ?? '') ?? 0) - (originalFileOrder.current.get(b.uri ?? '') ?? 0)); + proceedWithFilesAction(sortedFiles); resetValidationState(); } }, [deduplicateErrors, pdfFilesToRender.length, proceedWithFilesAction, resetValidationState]); @@ -169,6 +179,11 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi // Reset collected errors for new validation collectedErrors.current = []; + // Store original file order using URI as key + files.forEach((file, index) => { + originalFileOrder.current.set(file.uri ?? '', index); + }); + Promise.all(files.map((file, index) => isValidFile(file, items.at(index), files.length > 1).then((isValid) => (isValid ? file : null)))) .then((validationResults) => { const filteredResults = validationResults.filter((result): result is FileObject => result !== null); @@ -180,9 +195,18 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsLoaderVisible(true); return Promise.all(otherFiles.map((file) => convertHeicImageToJpegPromise(file))).then((convertedImages) => { + // Update originalFileOrder map with converted files + convertedImages.forEach((convertedFile, index) => { + updateFileOrderMapping(otherFiles.at(index), convertedFile); + }); + // Check if we need to resize images if (convertedImages.some((file) => (file.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE)) { return Promise.all(convertedImages.map((file) => resizeImageIfNeeded(file))).then((processedFiles) => { + // Update originalFileOrder map with resized files + processedFiles.forEach((resizedFile, index) => { + updateFileOrderMapping(convertedImages.at(index), resizedFile); + }); setIsLoaderVisible(false); return Promise.resolve({processedFiles, pdfsToLoad}); }); @@ -198,6 +222,10 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi if (otherFiles.some((file) => (file.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE)) { setIsLoaderVisible(true); return Promise.all(otherFiles.map((file) => resizeImageIfNeeded(file))).then((processedFiles) => { + // Update originalFileOrder map with resized files + processedFiles.forEach((resizedFile, index) => { + updateFileOrderMapping(otherFiles.at(index), resizedFile); + }); setIsLoaderVisible(false); return Promise.resolve({processedFiles, pdfsToLoad}); }); @@ -228,7 +256,8 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsErrorModalVisible(true); } } else if (processedFiles.length > 0) { - proceedWithFilesAction(processedFiles); + const sortedFiles = processedFiles.sort((a, b) => (originalFileOrder.current.get(a.uri ?? '') ?? 0) - (originalFileOrder.current.get(b.uri ?? '') ?? 0)); + proceedWithFilesAction(sortedFiles); resetValidationState(); } } @@ -242,7 +271,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi if (files.length > CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT) { filesToValidate.current = files.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT); if (items) { - dataTransferItemList.current = items.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT) + dataTransferItemList.current = items.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT); } setErrorAndOpenModal(CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED); } else { @@ -271,16 +300,17 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi } } + const sortedFiles = validFilesToUpload.sort((a, b) => (originalFileOrder.current.get(a.uri ?? '') ?? 0) - (originalFileOrder.current.get(b.uri ?? '') ?? 0)); // If we're validating attachments we need to use InteractionManager to ensure // the error modal is dismissed before opening the attachment modal if (!isValidatingReceipts && fileError) { setIsErrorModalVisible(false); InteractionManager.runAfterInteractions(() => { - proceedWithFilesAction(validFilesToUpload); + proceedWithFilesAction(sortedFiles); resetValidationState(); }); } else { - proceedWithFilesAction(validFilesToUpload); + proceedWithFilesAction(sortedFiles); hideModalAndReset(); } }; From 77166e532d430942449735a66074705d17abaae6 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 21 Jul 2025 10:59:45 +0200 Subject: [PATCH 06/13] fix: minor fix --- src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 8ac9942769a5..47a4802125af 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -966,7 +966,7 @@ function IOURequestStepConfirmation({ const file = e?.dataTransfer?.files[0]; if (file) { file.uri = URL.createObjectURL(file); - validateFiles([file], e.dataTransfer?.items); + validateFiles([file], Array.from(e.dataTransfer?.items)); } }; From 2a43ea020550bde14e8f947bbffce350341eae4a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 21 Jul 2025 13:11:05 +0200 Subject: [PATCH 07/13] fix: minor fix --- ios/NewExpensify.xcodeproj/project.pbxproj | 4 ++-- ios/Podfile.lock | 20 +++---------------- .../step/IOURequestStepScan/index.native.tsx | 1 + 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 3f8f534b9450..bdc70c0f3d76 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 383643682B6D4AE2005BB9AE /* DeviceCheck.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 383643672B6D4AE2005BB9AE /* DeviceCheck.framework */; }; 47347EFF2DA5664A00633001 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47347EFE2DA5664A00633001 /* AppDelegate.swift */; }; 524F95D57E75496EBD14B0AA /* ExpensifyMono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */; }; + 59164B2F48344A53975791A9 /* CustomEmojiNativeFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */; }; 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; 7F9DD8DA2B2A445B005E3AFA /* ExpError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9DD8D92B2A445B005E3AFA /* ExpError.swift */; }; @@ -48,7 +49,6 @@ ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; FF941A8D48F849269AB85C9A /* ExpensifyNewKansas-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */; }; - 59164B2F48344A53975791A9 /* CustomEmojiNativeFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -138,6 +138,7 @@ 9196A72C11B91A52A43D6E8A /* libPods-NotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyMono-BoldItalic.otf"; sourceTree = ""; }; AC131FBA2CF634F20010CE80 /* BackgroundTasks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTasks.framework; path = System/Library/Frameworks/BackgroundTasks.framework; sourceTree = SDKROOT; }; + B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = CustomEmojiNativeFont.ttf; path = ../assets/fonts/native/CustomEmojiNativeFont.ttf; sourceTree = ""; }; BBE493797E97F2995E627244 /* Pods-NotificationServiceExtension.debugadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debugadhoc.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debugadhoc.xcconfig"; sourceTree = ""; }; BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; }; D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-MediumItalic.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf"; sourceTree = ""; }; @@ -159,7 +160,6 @@ F0C450E92705020500FD2970 /* colors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = colors.json; path = ../colors.json; sourceTree = ""; }; F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; }; F8839E9820F4C312BD1C9339 /* Pods-NewExpensify.releasedevelopment.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.releasedevelopment.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.releasedevelopment.xcconfig"; sourceTree = ""; }; - B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */ = {isa = PBXFileReference; name = "CustomEmojiNativeFont.ttf"; path = "../assets/fonts/native/CustomEmojiNativeFont.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a11a601cdcc..5d59ac10a1d5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -487,7 +487,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -506,7 +505,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -524,7 +522,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -544,7 +541,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -563,7 +559,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -582,7 +577,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -601,7 +595,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -620,7 +613,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -639,7 +631,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -658,7 +649,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -677,7 +667,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -696,7 +685,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -715,7 +703,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -734,7 +721,6 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger - - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -3776,7 +3762,7 @@ SPEC CHECKSUMS: RCTTypeSafety: 659ae318c09de0477fd27bbc9e140071c7ea5c93 React: c2d3aa44c49bb34e4dfd49d3ee92da5ebacc1c1c React-callinvoker: 1bdfb7549b5af266d85757193b5069f60659ef9d - React-Core: 7e3642ef83e1032df3d25ebc840868c0c54381ca + React-Core: 7150cf9b6a5af063b37003062689f1691e79c020 React-CoreModules: 15a85e6665d61678942da6ae485b351f4c699049 React-cxxreact: 74f9de59259ac951923f5726aa14f0398f167af9 React-debug: e74e76912b91e08d580c481c34881899ccf63da9 @@ -3825,7 +3811,7 @@ SPEC CHECKSUMS: react-native-webview: 0f56a0e7348f945abdc92546dc62ce93d083fa75 React-NativeModulesApple: 452b86b29fae99ed0a4015dca3ad9cd222f88abf React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c - React-perflogger: 005c6dfd4aa335fd527c94cc4591ed9b509f0486 + React-perflogger: 6fd2f6811533e9c19a61e855c3033eecbf4ad2a0 React-performancetimeline: abf31259d794c9274b3ea19c5016186925eec6c4 React-RCTActionSheet: a499b0d6d9793886b67ba3e16046a3fef2cdbbc3 React-RCTAnimation: 2595dcb10a82216a511b54742f8c28d793852ac6 @@ -3840,7 +3826,7 @@ SPEC CHECKSUMS: React-RCTSettings: a060c7e381a3896104761b8eed7e284d95e37df3 React-RCTText: 4f272b72dbb61f390d8c8274528f9fdbff983806 React-RCTVibration: 0e5326220719aca12473d703aa46693e3b4ce67a - React-rendererconsistency: 3a8c9e0828a14d477b44958581034915d86c1517 + React-rendererconsistency: 351fdbc5c1fe4da24243d939094a80f0e149c7a1 React-renderercss: d333f2ada83969591100d91ec6b23ca2e17e1507 React-rendererdebug: 039e5949b72ba63c703de020701e3fd152434c61 React-rncore: 57ed480649bb678d8bdc386d20fee8bf2b0c307c diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 50d3f9b8d3e2..53268458251f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -841,6 +841,7 @@ function IOURequestStepScan({ setIsLoaderVisible(true)} fileLimit={shouldAcceptMultipleFiles ? CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT : 1} + shouldValidateImage={false} > {({openPicker}) => ( Date: Mon, 21 Jul 2025 17:54:40 +0200 Subject: [PATCH 08/13] fix: minor improvements --- src/components/AttachmentComposerModal.tsx | 5 +---- src/hooks/useFilesValidation.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/AttachmentComposerModal.tsx b/src/components/AttachmentComposerModal.tsx index 0bc961ffb6b6..838ab171a7be 100644 --- a/src/components/AttachmentComposerModal.tsx +++ b/src/components/AttachmentComposerModal.tsx @@ -1,14 +1,12 @@ import React, {memo, useCallback, useEffect, useRef, useState} from 'react'; import type {View} from 'react-native'; -import {InteractionManager} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; import Animated, {FadeIn, LayoutAnimationConfig} from 'react-native-reanimated'; -import type {ValueOf} from 'type-fest'; import useFilesValidation from '@hooks/useFilesValidation'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import {cleanFileName, getFileValidationErrorText} from '@libs/fileDownload/FileUtils'; +import {cleanFileName} from '@libs/fileDownload/FileUtils'; import CONST from '@src/CONST'; import type ModalType from '@src/types/utils/ModalType'; import viewRef from '@src/types/utils/viewRef'; @@ -17,7 +15,6 @@ import useCarouselArrows from './Attachments/AttachmentCarousel/useCarouselArrow import useAttachmentErrors from './Attachments/AttachmentView/useAttachmentErrors'; import type {Attachment} from './Attachments/types'; import Button from './Button'; -import ConfirmModal from './ConfirmModal'; import HeaderGap from './HeaderGap'; import HeaderWithBackButton from './HeaderWithBackButton'; import Modal from './Modal'; diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx index deb49a6ea73f..144a57d2de46 100644 --- a/src/hooks/useFilesValidation.tsx +++ b/src/hooks/useFilesValidation.tsx @@ -27,6 +27,10 @@ type ErrorObject = { fileExtension?: string; }; +const sortFilesByOriginalOrder = (files: FileObject[], orderMap: Map) => { + return files.sort((a, b) => (orderMap.get(a.uri ?? '') ?? 0) - (orderMap.get(b.uri ?? '') ?? 0)); +}; + function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => void, isValidatingReceipts = true) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -164,7 +168,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsErrorModalVisible(true); } } else if (validFiles.current.length > 0) { - const sortedFiles = validFiles.current.sort((a, b) => (originalFileOrder.current.get(a.uri ?? '') ?? 0) - (originalFileOrder.current.get(b.uri ?? '') ?? 0)); + const sortedFiles = sortFilesByOriginalOrder(validFiles.current, originalFileOrder.current); proceedWithFilesAction(sortedFiles); resetValidationState(); } @@ -256,7 +260,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsErrorModalVisible(true); } } else if (processedFiles.length > 0) { - const sortedFiles = processedFiles.sort((a, b) => (originalFileOrder.current.get(a.uri ?? '') ?? 0) - (originalFileOrder.current.get(b.uri ?? '') ?? 0)); + const sortedFiles = sortFilesByOriginalOrder(processedFiles, originalFileOrder.current); proceedWithFilesAction(sortedFiles); resetValidationState(); } @@ -300,7 +304,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi } } - const sortedFiles = validFilesToUpload.sort((a, b) => (originalFileOrder.current.get(a.uri ?? '') ?? 0) - (originalFileOrder.current.get(b.uri ?? '') ?? 0)); + const sortedFiles = sortFilesByOriginalOrder(validFilesToUpload, originalFileOrder.current); // If we're validating attachments we need to use InteractionManager to ensure // the error modal is dismissed before opening the attachment modal if (!isValidatingReceipts && fileError) { From a2b0b444e343c07ca24e6f9c5d9abc693bb8f68d Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 21 Jul 2025 19:08:54 +0200 Subject: [PATCH 09/13] fix: revert podfile --- ios/NewExpensify.xcodeproj/project.pbxproj | 4 ++-- ios/Podfile.lock | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index bdc70c0f3d76..3f8f534b9450 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -25,7 +25,6 @@ 383643682B6D4AE2005BB9AE /* DeviceCheck.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 383643672B6D4AE2005BB9AE /* DeviceCheck.framework */; }; 47347EFF2DA5664A00633001 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47347EFE2DA5664A00633001 /* AppDelegate.swift */; }; 524F95D57E75496EBD14B0AA /* ExpensifyMono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */; }; - 59164B2F48344A53975791A9 /* CustomEmojiNativeFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */; }; 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7041848426A8E47D00E09F4D /* RCTStartupTimer.m */; }; 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; 7F9DD8DA2B2A445B005E3AFA /* ExpError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9DD8D92B2A445B005E3AFA /* ExpError.swift */; }; @@ -49,6 +48,7 @@ ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; FF941A8D48F849269AB85C9A /* ExpensifyNewKansas-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */; }; + 59164B2F48344A53975791A9 /* CustomEmojiNativeFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -138,7 +138,6 @@ 9196A72C11B91A52A43D6E8A /* libPods-NotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-BoldItalic.otf"; path = "../assets/fonts/native/ExpensifyMono-BoldItalic.otf"; sourceTree = ""; }; AC131FBA2CF634F20010CE80 /* BackgroundTasks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTasks.framework; path = System/Library/Frameworks/BackgroundTasks.framework; sourceTree = SDKROOT; }; - B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = CustomEmojiNativeFont.ttf; path = ../assets/fonts/native/CustomEmojiNativeFont.ttf; sourceTree = ""; }; BBE493797E97F2995E627244 /* Pods-NotificationServiceExtension.debugadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debugadhoc.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debugadhoc.xcconfig"; sourceTree = ""; }; BF6A4C5167244B9FB8E4D4E3 /* ExpensifyNeue-Italic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Italic.otf"; path = "../assets/fonts/native/ExpensifyNeue-Italic.otf"; sourceTree = ""; }; D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-MediumItalic.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-MediumItalic.otf"; sourceTree = ""; }; @@ -160,6 +159,7 @@ F0C450E92705020500FD2970 /* colors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = colors.json; path = ../colors.json; sourceTree = ""; }; F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Regular.otf"; path = "../assets/fonts/native/ExpensifyNeue-Regular.otf"; sourceTree = ""; }; F8839E9820F4C312BD1C9339 /* Pods-NewExpensify.releasedevelopment.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.releasedevelopment.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.releasedevelopment.xcconfig"; sourceTree = ""; }; + B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */ = {isa = PBXFileReference; name = "CustomEmojiNativeFont.ttf"; path = "../assets/fonts/native/CustomEmojiNativeFont.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5d59ac10a1d5..3a11a601cdcc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -487,6 +487,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -505,6 +506,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -522,6 +524,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -541,6 +544,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -559,6 +563,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -577,6 +582,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -595,6 +601,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -613,6 +620,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -631,6 +639,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -649,6 +658,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -667,6 +677,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -685,6 +696,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -703,6 +715,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -721,6 +734,7 @@ PODS: - React-jsinspector - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimescheduler - React-utils - SocketRocket (= 0.7.1) @@ -3762,7 +3776,7 @@ SPEC CHECKSUMS: RCTTypeSafety: 659ae318c09de0477fd27bbc9e140071c7ea5c93 React: c2d3aa44c49bb34e4dfd49d3ee92da5ebacc1c1c React-callinvoker: 1bdfb7549b5af266d85757193b5069f60659ef9d - React-Core: 7150cf9b6a5af063b37003062689f1691e79c020 + React-Core: 7e3642ef83e1032df3d25ebc840868c0c54381ca React-CoreModules: 15a85e6665d61678942da6ae485b351f4c699049 React-cxxreact: 74f9de59259ac951923f5726aa14f0398f167af9 React-debug: e74e76912b91e08d580c481c34881899ccf63da9 @@ -3811,7 +3825,7 @@ SPEC CHECKSUMS: react-native-webview: 0f56a0e7348f945abdc92546dc62ce93d083fa75 React-NativeModulesApple: 452b86b29fae99ed0a4015dca3ad9cd222f88abf React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c - React-perflogger: 6fd2f6811533e9c19a61e855c3033eecbf4ad2a0 + React-perflogger: 005c6dfd4aa335fd527c94cc4591ed9b509f0486 React-performancetimeline: abf31259d794c9274b3ea19c5016186925eec6c4 React-RCTActionSheet: a499b0d6d9793886b67ba3e16046a3fef2cdbbc3 React-RCTAnimation: 2595dcb10a82216a511b54742f8c28d793852ac6 @@ -3826,7 +3840,7 @@ SPEC CHECKSUMS: React-RCTSettings: a060c7e381a3896104761b8eed7e284d95e37df3 React-RCTText: 4f272b72dbb61f390d8c8274528f9fdbff983806 React-RCTVibration: 0e5326220719aca12473d703aa46693e3b4ce67a - React-rendererconsistency: 351fdbc5c1fe4da24243d939094a80f0e149c7a1 + React-rendererconsistency: 3a8c9e0828a14d477b44958581034915d86c1517 React-renderercss: d333f2ada83969591100d91ec6b23ca2e17e1507 React-rendererdebug: 039e5949b72ba63c703de020701e3fd152434c61 React-rncore: 57ed480649bb678d8bdc386d20fee8bf2b0c307c From adaf44803a3121b679f92abdbae1c15df0818711 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 21 Jul 2025 19:26:01 +0200 Subject: [PATCH 10/13] fix: lint --- src/components/AttachmentModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 4edfce8a4726..188c4ab4a00a 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -1,4 +1,3 @@ -import {Str} from 'expensify-common'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, Keyboard, View} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; From bdeacaf299a8692669f5e909c2871e6f4db2da30 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 22 Jul 2025 11:10:36 +0200 Subject: [PATCH 11/13] fix: address comments --- src/hooks/useFilesValidation.tsx | 8 ++++++-- src/libs/fileDownload/FileUtils.ts | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx index 144a57d2de46..6ef8b3e1cd7b 100644 --- a/src/hooks/useFilesValidation.tsx +++ b/src/hooks/useFilesValidation.tsx @@ -310,11 +310,15 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi if (!isValidatingReceipts && fileError) { setIsErrorModalVisible(false); InteractionManager.runAfterInteractions(() => { - proceedWithFilesAction(sortedFiles); + if (sortedFiles.length !== 0) { + proceedWithFilesAction(sortedFiles); + } resetValidationState(); }); } else { - proceedWithFilesAction(sortedFiles); + if (sortedFiles.length !== 0) { + proceedWithFilesAction(sortedFiles); + } hideModalAndReset(); } }; diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index cb826e498ac7..64268144d77f 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -464,6 +464,7 @@ const getFileValidationErrorText = ( reason: '', }; } + const maxSize = isValidatingReceipt ? CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE : CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE; switch (validationError) { case CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE: return { @@ -488,7 +489,7 @@ const getFileValidationErrorText = ( return { title: translateLocal('attachmentPicker.someFilesCantBeUploaded'), reason: translateLocal('attachmentPicker.sizeLimitExceeded', { - maxUploadSizeInMB: additionalData.maxUploadSizeInMB ?? CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE / 1024 / 1024, + maxUploadSizeInMB: additionalData.maxUploadSizeInMB ?? maxSize / 1024 / 1024, }), }; case CONST.FILE_VALIDATION_ERRORS.FILE_TOO_SMALL: From 999d5665b74bc34efc7aa35b0fd197513dea69c8 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 22 Jul 2025 12:20:20 +0200 Subject: [PATCH 12/13] fix: error modal buttons --- src/hooks/useFilesValidation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx index 6ef8b3e1cd7b..2de3809e3b9c 100644 --- a/src/hooks/useFilesValidation.tsx +++ b/src/hooks/useFilesValidation.tsx @@ -294,7 +294,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi const nextIndex = currentErrorIndex + 1; const nextError = errorQueue.at(nextIndex); if (nextError) { - if (validFiles.current.length === 0) { + if (isValidatingMultipleFiles && currentErrorIndex === errorQueue.length - 2 && validFilesToUpload.length === 0) { setIsValidatingMultipleFiles(false); } setCurrentErrorIndex(nextIndex); From b94561e90127d909fe790482a64e2f24cf510391 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 23 Jul 2025 12:42:02 +0200 Subject: [PATCH 13/13] fix: remove redundant comments --- src/hooks/useFilesValidation.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/hooks/useFilesValidation.tsx b/src/hooks/useFilesValidation.tsx index 2de3809e3b9c..d047edbb5031 100644 --- a/src/hooks/useFilesValidation.tsx +++ b/src/hooks/useFilesValidation.tsx @@ -183,7 +183,6 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi // Reset collected errors for new validation collectedErrors.current = []; - // Store original file order using URI as key files.forEach((file, index) => { originalFileOrder.current.set(file.uri ?? '', index); }); @@ -199,7 +198,6 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi setIsLoaderVisible(true); return Promise.all(otherFiles.map((file) => convertHeicImageToJpegPromise(file))).then((convertedImages) => { - // Update originalFileOrder map with converted files convertedImages.forEach((convertedFile, index) => { updateFileOrderMapping(otherFiles.at(index), convertedFile); }); @@ -207,7 +205,6 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi // Check if we need to resize images if (convertedImages.some((file) => (file.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE)) { return Promise.all(convertedImages.map((file) => resizeImageIfNeeded(file))).then((processedFiles) => { - // Update originalFileOrder map with resized files processedFiles.forEach((resizedFile, index) => { updateFileOrderMapping(convertedImages.at(index), resizedFile); }); @@ -226,7 +223,6 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi if (otherFiles.some((file) => (file.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE)) { setIsLoaderVisible(true); return Promise.all(otherFiles.map((file) => resizeImageIfNeeded(file))).then((processedFiles) => { - // Update originalFileOrder map with resized files processedFiles.forEach((resizedFile, index) => { updateFileOrderMapping(otherFiles.at(index), resizedFile); });