Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When uploading, automatically reduce the size of receipt image #45448

Merged
merged 16 commits into from
Aug 5, 2024
Merged
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ const CONST = {

LOGO_MAX_SCALE: 1.5,

MAX_IMAGE_DIMENSION: 2400,

BREADCRUMB_TYPE: {
ROOT: 'root',
STRONG: 'strong',
Expand Down
24 changes: 24 additions & 0 deletions src/libs/fileDownload/FileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DateUtils from '@libs/DateUtils';
import * as Localize from '@libs/Localize';
import Log from '@libs/Log';
import CONST from '@src/CONST';
import getImageManipulator from './getImageManipulator';
import getImageResolution from './getImageResolution';
import type {ReadFileAsync, SplitExtensionFromFileName} from './types';

Expand Down Expand Up @@ -285,6 +286,27 @@ function isHighResolutionImage(resolution: {width: number; height: number} | nul
return resolution !== null && (resolution.width > CONST.IMAGE_HIGH_RESOLUTION_THRESHOLD || resolution.height > CONST.IMAGE_HIGH_RESOLUTION_THRESHOLD);
}

const getImageDimensionsAfterResize = (file: FileObject) =>
ImageSize.getSize(file.uri ?? '').then(({width, height}) => {
let newWidth;
let newHeight;
if (width < height) {
newHeight = CONST.MAX_IMAGE_DIMENSION;
newWidth = (newHeight * width) / height;
} else {
newWidth = CONST.MAX_IMAGE_DIMENSION;
newHeight = (newWidth * height) / width;
}

return {width: newWidth, height: newHeight};
});

const resizeImageIfNeeded = (file: FileObject) => {
if (!file || !Str.isImage(file.name ?? '') || (file?.size ?? 0) <= CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1206agra I think any image over 2MB should do it

@luacmartins This is not true. I need 24MB file to check this flow. See this line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do you want to manipulate this condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1206agra you can download the large image here

return Promise.resolve(file);
}
return getImageDimensionsAfterResize(file).then(({width, height}) => getImageManipulator({fileUri: file.uri ?? '', width, height, fileName: file.name ?? '', type: file.type}));
};
export {
showGeneralErrorAlert,
showSuccessAlert,
Expand All @@ -302,4 +324,6 @@ export {
isImage,
getFileResolution,
isHighResolutionImage,
getImageDimensionsAfterResize,
resizeImageIfNeeded,
};
13 changes: 13 additions & 0 deletions src/libs/fileDownload/getImageManipulator/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {manipulateAsync} from 'expo-image-manipulator';
import type {FileObject} from '@components/AttachmentModal';
import type ImageManipulatorConfig from './type';

export default function getImageManipulator({fileUri, width, height, type, fileName}: ImageManipulatorConfig): Promise<FileObject> {
return manipulateAsync(fileUri ?? '', [{resize: {width, height}}]).then((result) => ({
uri: result.uri,
width: result.width,
height: result.height,
type,
name: fileName,
}));
}
14 changes: 14 additions & 0 deletions src/libs/fileDownload/getImageManipulator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {manipulateAsync} from 'expo-image-manipulator';
import type ImageManipulatorConfig from './type';

export default function getImageManipulator({fileUri, width, height, fileName}: ImageManipulatorConfig): Promise<File> {
return manipulateAsync(fileUri ?? '', [{resize: {width, height}}]).then((result) =>
fetch(result.uri)
.then((res) => res.blob())
.then((blob) => {
const resizedFile = new File([blob], `${fileName}.jpeg`, {type: 'image/jpeg'});
resizedFile.uri = URL.createObjectURL(resizedFile);
return resizedFile;
}),
);
}
9 changes: 9 additions & 0 deletions src/libs/fileDownload/getImageManipulator/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type ImageManipulatorConfig = {
fileUri: string;
fileName: string;
width: number;
height: number;
type?: string;
};

export default ImageManipulatorConfig;
30 changes: 16 additions & 14 deletions src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ function IOURequestStepScan({
return false;
}

if ((file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
if (!Str.isImage(file.name ?? '') && (file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
Alert.alert(translate('attachmentPicker.attachmentTooLarge'), translate('attachmentPicker.sizeExceeded'));
return false;
}
Expand Down Expand Up @@ -387,28 +387,30 @@ function IOURequestStepScan({
/**
* Sets the Receipt objects and navigates the user to the next page
*/
const setReceiptAndNavigate = (file: FileObject, isPdfValidated?: boolean) => {
if (!validateReceipt(file)) {
const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => {
if (!validateReceipt(originalFile)) {
return;
}

// If we have a pdf file and if it is not validated then set the pdf file for validation and return
if (Str.isPDF(file.name ?? '') && !isPdfValidated) {
setPdfFile(file);
if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) {
setPdfFile(originalFile);
return;
}

// Store the receipt on the transaction object in Onyx
// On Android devices, fetching blob for a file with name containing spaces fails to retrieve the type of file.
// So, let us also save the file type in receipt for later use during blob fetch
IOU.setMoneyRequestReceipt(transactionID, file?.uri ?? '', file.name ?? '', action !== CONST.IOU.ACTION.EDIT, file.type);
FileUtils.resizeImageIfNeeded(originalFile).then((file) => {
// Store the receipt on the transaction object in Onyx
// On Android devices, fetching blob for a file with name containing spaces fails to retrieve the type of file.
// So, let us also save the file type in receipt for later use during blob fetch
IOU.setMoneyRequestReceipt(transactionID, file?.uri ?? '', file.name ?? '', action !== CONST.IOU.ACTION.EDIT, file.type);

if (action === CONST.IOU.ACTION.EDIT) {
updateScanAndNavigate(file, file?.uri ?? '');
return;
}
if (action === CONST.IOU.ACTION.EDIT) {
updateScanAndNavigate(file, file?.uri ?? '');
return;
}

navigateToConfirmationStep(file, file.uri ?? '');
navigateToConfirmationStep(file, file.uri ?? '');
});
};

const capturePhoto = useCallback(() => {
Expand Down
33 changes: 18 additions & 15 deletions src/pages/iou/request/step/IOURequestStepScan/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ function IOURequestStepScan({
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();
const {isDraggingOver} = useContext(DragAndDropContext);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`);
const [cameraPermissionState, setCameraPermissionState] = useState<PermissionState | undefined>('prompt');
const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false);
const [isTorchAvailable, setIsTorchAvailable] = useState(false);
Expand All @@ -78,6 +77,7 @@ function IOURequestStepScan({
const [isQueriedPermissionState, setIsQueriedPermissionState] = useState(false);

const getScreenshotTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`);

const [videoConstraints, setVideoConstraints] = useState<MediaTrackConstraints>();
const tabIndex = 1;
Expand Down Expand Up @@ -207,7 +207,7 @@ function IOURequestStepScan({
return false;
}

if ((file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
if (!Str.isImage(file.name ?? '') && (file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceeded');
return false;
}
Expand Down Expand Up @@ -419,27 +419,30 @@ function IOURequestStepScan({
/**
* Sets the Receipt objects and navigates the user to the next page
*/
const setReceiptAndNavigate = (file: FileObject, isPdfValidated?: boolean) => {
validateReceipt(file).then((isFileValid) => {
const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => {
validateReceipt(originalFile).then((isFileValid) => {
if (!isFileValid) {
return;
}

// If we have a pdf file and if it is not validated then set the pdf file for validation and return
if (Str.isPDF(file.name ?? '') && !isPdfValidated) {
setPdfFile(file);
if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) {
setPdfFile(originalFile);
return;
}
// Store the receipt on the transaction object in Onyx
const source = URL.createObjectURL(file as Blob);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
IOU.setMoneyRequestReceipt(transactionID, source, file.name || '', action !== CONST.IOU.ACTION.EDIT);

if (action === CONST.IOU.ACTION.EDIT) {
updateScanAndNavigate(file, source);
return;
}
navigateToConfirmationStep(file, source);
FileUtils.resizeImageIfNeeded(originalFile).then((file) => {
// Store the receipt on the transaction object in Onyx
const source = URL.createObjectURL(file as Blob);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
IOU.setMoneyRequestReceipt(transactionID, source, file.name || '', action !== CONST.IOU.ACTION.EDIT);

if (action === CONST.IOU.ACTION.EDIT) {
updateScanAndNavigate(file, source);
return;
}
navigateToConfirmationStep(file, source);
});
});
};

Expand Down
Loading