11import { Str } from 'expensify-common' ;
2- import React , { useRef , useState } from 'react' ;
2+ import React , { useEffect , useRef , useState } from 'react' ;
33import { InteractionManager } from 'react-native' ;
44import type { ValueOf } from 'type-fest' ;
55import ConfirmModal from '@components/ConfirmModal' ;
@@ -18,6 +18,7 @@ import useLocalize from './useLocalize';
1818import useThemeStyles from './useThemeStyles' ;
1919
2020const DEFAULT_IS_VALIDATING_RECEIPTS = true ;
21+ const MIN_LOADER_VISIBLE_DURATION_MS = 200 ;
2122
2223type ValidationOptions = {
2324 isValidatingReceipts ?: boolean ;
@@ -56,6 +57,7 @@ function useFilesValidation(onFilesValidated: (files: FileObject[], dataTransfer
5657 const dataTransferItemList = useRef < DataTransferItem [ ] > ( [ ] ) ;
5758 const collectedErrors = useRef < FileValidationError [ ] > ( [ ] ) ;
5859 const originalFileOrder = useRef < Map < string , number > > ( new Map ( ) ) ;
60+ const loaderTimeoutRef = useRef < NodeJS . Timeout | undefined > ( undefined ) ;
5961
6062 const updateFileOrderMapping = ( oldFile : FileObject | undefined , newFile : FileObject ) => {
6163 const originalIndex = originalFileOrder . current . get ( oldFile ?. uri ?? '' ) ;
@@ -76,6 +78,16 @@ function useFilesValidation(onFilesValidated: (files: FileObject[], dataTransfer
7678 } ) ;
7779 } ;
7880
81+ useEffect ( ( ) => {
82+ return ( ) => {
83+ if ( ! loaderTimeoutRef . current ) {
84+ return ;
85+ }
86+ clearTimeout ( loaderTimeoutRef . current ) ;
87+ loaderTimeoutRef . current = undefined ;
88+ } ;
89+ } , [ ] ) ;
90+
7991 const reset = ( ) => {
8092 setIsValidatingFiles ( false ) ;
8193 setIsValidatingReceipts ( undefined ) ;
@@ -141,6 +153,14 @@ function useFilesValidation(onFilesValidated: (files: FileObject[], dataTransfer
141153 return ;
142154 }
143155
156+ let loaderStartTime : number | undefined ;
157+ const showLoader = ( ) => {
158+ if ( loaderStartTime === undefined ) {
159+ loaderStartTime = Date . now ( ) ;
160+ }
161+ setIsLoaderVisible ( true ) ;
162+ } ;
163+
144164 // Reset collected errors for new validation
145165 collectedErrors . current = [ ] ;
146166
@@ -186,7 +206,7 @@ function useFilesValidation(onFilesValidated: (files: FileObject[], dataTransfer
186206 ) ;
187207
188208 if ( filesToConvert . length > 0 ) {
189- setIsLoaderVisible ( true ) ;
209+ showLoader ( ) ;
190210
191211 const convertedFilesToResize : FileObject [ ] = [ ] ;
192212 const convertedFiles : FileObject [ ] = [ ] ;
@@ -233,7 +253,7 @@ function useFilesValidation(onFilesValidated: (files: FileObject[], dataTransfer
233253 }
234254
235255 if ( filesToResize . length > 0 ) {
236- setIsLoaderVisible ( true ) ;
256+ showLoader ( ) ;
237257
238258 const toResizeResults = await Promise . allSettled ( filesToResize . map ( ( file ) => resizeImageIfNeeded ( file ) ) ) ;
239259
@@ -253,32 +273,58 @@ function useFilesValidation(onFilesValidated: (files: FileObject[], dataTransfer
253273 }
254274 }
255275
256- setIsLoaderVisible ( false ) ;
276+ const handleNext = ( ) => {
277+ if ( pdfsToLoad . length ) {
278+ validFiles . current = validNonPdfFiles ;
279+ setPdfFilesToRender ( pdfsToLoad ) ;
280+ return ;
281+ }
257282
258- if ( pdfsToLoad . length ) {
259- validFiles . current = validNonPdfFiles ;
260- setPdfFilesToRender ( pdfsToLoad ) ;
261- return ;
262- }
283+ if ( validNonPdfFiles . length > 0 ) {
284+ setValidFilesToUpload ( validNonPdfFiles ) ;
285+ }
263286
264- if ( validNonPdfFiles . length > 0 ) {
265- setValidFilesToUpload ( validNonPdfFiles ) ;
266- }
287+ if ( collectedErrors . current . length > 0 ) {
288+ const uniqueErrors = deduplicateErrors ( collectedErrors . current ) ;
289+ setErrorQueue ( uniqueErrors ) ;
290+ setCurrentErrorIndex ( 0 ) ;
291+ const firstError = uniqueErrors . at ( 0 ) ;
292+ if ( firstError ) {
293+ setFileError ( firstError ) ;
294+ setIsErrorModalVisible ( true ) ;
295+ }
296+ } else if ( validNonPdfFiles . length > 0 ) {
297+ const sortedFiles = sortFilesByOriginalOrder ( validNonPdfFiles , originalFileOrder . current ) ;
298+ onFilesValidated ( sortedFiles , dataTransferItemList . current ) ;
299+ reset ( ) ;
300+ }
301+ } ;
267302
268- if ( collectedErrors . current . length > 0 ) {
269- const uniqueErrors = Array . from ( new Set ( collectedErrors . current . map ( ( error ) => JSON . stringify ( error ) ) ) ) . map ( ( errorStr ) => JSON . parse ( errorStr ) as FileValidationError ) ;
270- setErrorQueue ( uniqueErrors ) ;
271- setCurrentErrorIndex ( 0 ) ;
272- const firstError = uniqueErrors . at ( 0 ) ;
273- if ( firstError ) {
274- setFileError ( firstError ) ;
275- setIsErrorModalVisible ( true ) ;
303+ const hideLoaderAndHandleNext = ( ) => {
304+ setIsLoaderVisible ( false ) ;
305+ handleNext ( ) ;
306+ } ;
307+
308+ const extendLoaderIfNeeded = ( ) => {
309+ if ( loaderStartTime === undefined ) {
310+ hideLoaderAndHandleNext ( ) ;
311+ return ;
276312 }
277- } else if ( validNonPdfFiles . length > 0 ) {
278- const sortedFiles = sortFilesByOriginalOrder ( validNonPdfFiles , originalFileOrder . current ) ;
279- onFilesValidated ( sortedFiles , dataTransferItemList . current ) ;
280- reset ( ) ;
281- }
313+
314+ const elapsedTime = Date . now ( ) - loaderStartTime ;
315+ const shouldDelayHide = collectedErrors . current . length > 0 && elapsedTime < MIN_LOADER_VISIBLE_DURATION_MS ;
316+
317+ if ( ! shouldDelayHide ) {
318+ hideLoaderAndHandleNext ( ) ;
319+ return ;
320+ }
321+
322+ loaderTimeoutRef . current = setTimeout ( ( ) => {
323+ hideLoaderAndHandleNext ( ) ;
324+ } , MIN_LOADER_VISIBLE_DURATION_MS - elapsedTime ) ;
325+ } ;
326+
327+ extendLoaderIfNeeded ( ) ;
282328 }
283329
284330 const validateFiles = ( files : FileObject [ ] , items ?: DataTransferItem [ ] , validationOptions ?: ValidationOptions ) => {
0 commit comments