From 32a2cc3de30369be4ba94370abc9ad455a89f851 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 7 Mar 2024 12:25:57 +0300 Subject: [PATCH 1/7] allow split bill --- src/pages/iou/request/step/IOURequestStepTag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index af1de64f8930..b9df563f6e46 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -81,7 +81,7 @@ function IOURequestStepTag({ const parentReportAction = parentReportActions[report.parentReportActionID]; // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && !canEditMoneyRequest(parentReportAction); + const shouldShowNotFoundPage = isEditing && !isSplitBill && !canEditMoneyRequest(parentReportAction); const navigateBack = () => { Navigation.goBack(backTo); From f26d231bd64a44e33fd8a238695c75e01322587e Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 7 Mar 2024 15:25:39 +0300 Subject: [PATCH 2/7] show not found page for read only split bill --- src/ROUTES.ts | 13 ++++++++--- .../MoneyRequestConfirmationList.js | 1 + .../iou/request/step/IOURequestStepTag.js | 22 +++++++++++++++---- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cfc287ba2cdc..bca7bb81dbd5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -385,9 +385,16 @@ const ROUTES = { getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAG: { - route: ':action/:iouType/tag/:tagIndex/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, tagIndex: number, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/tag/${tagIndex}/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/tag/:tagIndex/:transactionID/:reportID/:reportActionID?', + getRoute: ( + action: ValueOf, + iouType: ValueOf, + tagIndex: number, + transactionID: string, + reportID: string, + backTo = '', + reportActionID?: string, + ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${tagIndex}/${transactionID}/${reportID}${typeof reportActionID === 'string' ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index fa6de1c2e4f4..94a0c9c35280 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -797,6 +797,7 @@ function MoneyRequestConfirmationList(props) { props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams(), + props.reportActionID, ), ); return; diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index b9df563f6e46..18f8e68d67aa 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -47,6 +47,15 @@ const propTypes = { /** The actions from the parent report */ parentReportActions: PropTypes.shape(reportActionPropTypes), + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user accountID */ + accountID: PropTypes.number, + + /** Currently logged in user email */ + email: PropTypes.string, + }).isRequired, }; const defaultProps = { @@ -64,10 +73,11 @@ function IOURequestStepTag({ policyTags, report, route: { - params: {action, tagIndex: rawTagIndex, transactionID, backTo, iouType}, + params: {action, tagIndex: rawTagIndex, transactionID, backTo, iouType, reportActionID}, }, transaction, parentReportActions, + session, }) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -78,10 +88,11 @@ function IOURequestStepTag({ const tag = TransactionUtils.getTag(transaction, tagIndex); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; - const parentReportAction = parentReportActions[report.parentReportActionID]; + const parentReportAction = parentReportActions[report.parentReportActionID || reportActionID]; + const canEditSplitBill = isSplitBill && parentReportAction && session.accountID === parentReportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && !isSplitBill && !canEditMoneyRequest(parentReportAction); + const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !canEditMoneyRequest(parentReportAction)); const navigateBack = () => { Navigation.goBack(backTo); @@ -151,8 +162,11 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID || report.reportID : '0'}`, canEvict: false, }, + session: { + key: ONYXKEYS.SESSION, + }, }), )(IOURequestStepTag); From de5958792d3c2c726751321d8bceb73851216342 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 7 Mar 2024 16:46:13 +0300 Subject: [PATCH 3/7] updated onyx key --- src/pages/iou/request/step/IOURequestStepTag.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index 18f8e68d67aa..fe44f91cda98 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -162,7 +162,18 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID || report.reportID : '0'}`, + key: ({ + report, + route: { + params: {action, iouType}, + }, + }) => { + let reportID = '0'; + if (action === CONST.IOU.ACTION.EDIT) { + reportID = iouType === CONST.IOU.TYPE.SPLIT ? report.reportID : report.parentReportID; + } + return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`; + }, canEvict: false, }, session: { From 93aea8d68ea23860c1c3c4118875a7fcc24bb4fa Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 7 Mar 2024 19:17:14 +0300 Subject: [PATCH 4/7] changed prop name --- src/pages/iou/request/step/IOURequestStepTag.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index fe44f91cda98..ffa764aa53d5 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -46,7 +46,7 @@ const propTypes = { policyTags: tagPropTypes, /** The actions from the parent report */ - parentReportActions: PropTypes.shape(reportActionPropTypes), + reportActions: PropTypes.shape(reportActionPropTypes), /** Session info for the currently logged in user. */ session: PropTypes.shape({ @@ -64,7 +64,7 @@ const defaultProps = { policyTags: null, policyCategories: null, transaction: {}, - parentReportActions: {}, + reportActions: {}, }; function IOURequestStepTag({ @@ -76,7 +76,7 @@ function IOURequestStepTag({ params: {action, tagIndex: rawTagIndex, transactionID, backTo, iouType, reportActionID}, }, transaction, - parentReportActions, + reportActions, session, }) { const styles = useThemeStyles(); @@ -88,11 +88,11 @@ function IOURequestStepTag({ const tag = TransactionUtils.getTag(transaction, tagIndex); const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; - const parentReportAction = parentReportActions[report.parentReportActionID || reportActionID]; - const canEditSplitBill = isSplitBill && parentReportAction && session.accountID === parentReportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); + const reportAction = reportActions[report.parentReportActionID || reportActionID]; + const canEditSplitBill = isSplitBill && reportAction && session.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !canEditMoneyRequest(parentReportAction)); + const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !canEditMoneyRequest(reportAction)); const navigateBack = () => { Navigation.goBack(backTo); @@ -161,7 +161,7 @@ export default compose( policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, - parentReportActions: { + reportActions: { key: ({ report, route: { From 5717f246b5bcedff12c722e0cd26f462db8ac57c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 7 Mar 2024 19:38:26 +0300 Subject: [PATCH 5/7] change condition --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index bca7bb81dbd5..0bdb77a3a360 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -394,7 +394,7 @@ const ROUTES = { reportID: string, backTo = '', reportActionID?: string, - ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${tagIndex}/${transactionID}/${reportID}${typeof reportActionID === 'string' ? `/${reportActionID}` : ''}`, backTo), + ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${tagIndex}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', From 52491bd5da97c3c5465d650eec8ee4119b584299 Mon Sep 17 00:00:00 2001 From: Rory Abraham <47436092+roryabraham@users.noreply.github.com> Date: Sun, 10 Mar 2024 10:23:31 -0700 Subject: [PATCH 6/7] Revert "[New architecture] Replace `@oguzhnatly/react-native-image-manipulator` with `expo-image-manipulator`" --- ios/Podfile.lock | 22 +-- package-lock.json | 27 +--- package.json | 2 +- ...react-native-image-manipulator+1.0.5.patch | 19 +++ src/CONST.ts | 6 - src/libs/cropOrRotateImage/getSaveFormat.ts | 17 --- src/libs/cropOrRotateImage/index.native.ts | 6 +- src/libs/cropOrRotateImage/index.ts | 136 ++++++++++++++++-- src/libs/cropOrRotateImage/types.ts | 25 ++-- 9 files changed, 170 insertions(+), 90 deletions(-) create mode 100644 patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch delete mode 100644 src/libs/cropOrRotateImage/getSaveFormat.ts diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 491ec28b59e5..c93dfba50f5a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -35,9 +35,6 @@ PODS: - EXAV (13.10.4): - ExpoModulesCore - ReactCommon/turbomodule/core - - EXImageLoader (4.6.0): - - ExpoModulesCore - - React-Core - Expo (50.0.4): - ExpoModulesCore - ExpoImage (1.10.1): @@ -46,9 +43,6 @@ PODS: - SDWebImageAVIFCoder (~> 0.10.1) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.13.0) - - ExpoImageManipulator (11.8.0): - - EXImageLoader - - ExpoModulesCore - ExpoModulesCore (1.11.8): - glog - RCT-Folly (= 2022.05.16.00) @@ -1181,6 +1175,8 @@ PODS: - React-Core - react-native-geolocation (3.0.6): - React-Core + - react-native-image-manipulator (1.0.5): + - React - react-native-image-picker (7.0.3): - React-Core - react-native-key-command (1.0.6): @@ -1479,10 +1475,8 @@ DEPENDENCIES: - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXAV (from `../node_modules/expo-av/ios`) - - EXImageLoader (from `../node_modules/expo-image-loader/ios`) - Expo (from `../node_modules/expo`) - ExpoImage (from `../node_modules/expo-image/ios`) - - ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) @@ -1542,6 +1536,7 @@ DEPENDENCIES: - react-native-config (from `../node_modules/react-native-config`) - react-native-document-picker (from `../node_modules/react-native-document-picker`) - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" + - "react-native-image-manipulator (from `../node_modules/@oguzhnatly/react-native-image-manipulator`)" - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-key-command (from `../node_modules/react-native-key-command`) - react-native-launch-arguments (from `../node_modules/react-native-launch-arguments`) @@ -1663,14 +1658,10 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXAV: :path: "../node_modules/expo-av/ios" - EXImageLoader: - :path: "../node_modules/expo-image-loader/ios" Expo: :path: "../node_modules/expo" ExpoImage: :path: "../node_modules/expo-image/ios" - ExpoImageManipulator: - :path: "../node_modules/expo-image-manipulator/ios" ExpoModulesCore: :path: "../node_modules/expo-modules-core" FBLazyVector: @@ -1740,6 +1731,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-document-picker" react-native-geolocation: :path: "../node_modules/@react-native-community/geolocation" + react-native-image-manipulator: + :path: "../node_modules/@oguzhnatly/react-native-image-manipulator" react-native-image-picker: :path: "../node_modules/react-native-image-picker" react-native-key-command: @@ -1867,10 +1860,8 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 EXAV: 09a4d87fa6b113fbb0ada3aade6799f78271cb44 - EXImageLoader: 55080616b2fe9da19ef8c7f706afd9814e279b6b Expo: 1e3bcf9dd99de57a636127057f6b488f0609681a ExpoImage: 1cdaa65a6a70bb01067e21ad1347ff2d973885f5 - ExpoImageManipulator: c1d7cb865eacd620a35659f3da34c70531f10b59 ExpoModulesCore: 96d1751929ad10622773bb729ab28a8423f0dd0c FBLazyVector: fbc4957d9aa695250b55d879c1d86f79d7e69ab4 FBReactNativeSpec: 86de768f89901ef6ed3207cd686362189d64ac88 @@ -1944,6 +1935,7 @@ SPEC CHECKSUMS: react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e react-native-document-picker: 3599b238843369026201d2ef466df53f77ae0452 react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903 + react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56 react-native-image-picker: 2381c008bbb09e72395a2d043c147b11bd1523d9 react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d @@ -2007,7 +1999,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a - Yoga: 13c8ef87792450193e117976337b8527b49e8c03 + Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 diff --git a/package-lock.json b/package-lock.json index 8efb036099a9..133556e5de8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", "@kie/mock-github": "^1.0.0", + "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "10.6.0", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", @@ -55,7 +56,6 @@ "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", - "expo-image-manipulator": "11.8.0", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -8048,6 +8048,12 @@ "@octokit/openapi-types": "^12.11.0" } }, + "node_modules/@oguzhnatly/react-native-image-manipulator": { + "version": "1.0.5", + "resolved": "git+ssh://git@github.com/Expensify/react-native-image-manipulator.git#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", + "integrity": "sha512-C9Br1BQqm6io6lvYHptlLcOHbzlaqxp9tS35P8Qj3pdiiYRTzU3KPvZ61rQ+ZnZ4FOQ6MwPsKsmB8+6WHkAY6Q==", + "license": "MIT" + }, "node_modules/@onfido/active-video-capture": { "version": "0.28.6", "resolved": "https://registry.npmjs.org/@onfido/active-video-capture/-/active-video-capture-0.28.6.tgz", @@ -31144,25 +31150,6 @@ "expo": "*" } }, - "node_modules/expo-image-loader": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-4.6.0.tgz", - "integrity": "sha512-RHQTDak7/KyhWUxikn2yNzXL7i2cs16cMp6gEAgkHOjVhoCJQoOJ0Ljrt4cKQ3IowxgCuOrAgSUzGkqs7omj8Q==", - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-image-manipulator": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-11.8.0.tgz", - "integrity": "sha512-ZWVrHnYmwJq6h7auk+ropsxcNi+LyZcPFKQc8oy+JA0SaJosfShvkCm7RADWAunHmfPCmjHrhwPGEu/rs7WG/A==", - "dependencies": { - "expo-image-loader": "~4.6.0" - }, - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-keep-awake": { "version": "12.8.2", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-12.8.2.tgz", diff --git a/package.json b/package.json index 480806d7d111..1c0afbb5417b 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", "@kie/mock-github": "^1.0.0", + "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "10.6.0", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", @@ -104,7 +105,6 @@ "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", - "expo-image-manipulator": "11.8.0", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", diff --git a/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch b/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch new file mode 100644 index 000000000000..d5a390daf201 --- /dev/null +++ b/patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch @@ -0,0 +1,19 @@ +diff --git a/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle b/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle +index 3a1a548..fe030bb 100644 +--- a/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle ++++ b/node_modules/@oguzhnatly/react-native-image-manipulator/android/build.gradle +@@ -13,12 +13,12 @@ buildscript { + apply plugin: 'com.android.library' + + android { +- compileSdkVersion 28 ++ compileSdkVersion 34 + buildToolsVersion "28.0.3" + + defaultConfig { + minSdkVersion 16 +- targetSdkVersion 28 ++ targetSdkVersion 34 + versionCode 1 + versionName "1.0" + } diff --git a/src/CONST.ts b/src/CONST.ts index 645746c38a7d..ce2029c78713 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1035,12 +1035,6 @@ const CONST = { VIDEO: 'video', }, - IMAGE_FILE_FORMAT: { - PNG: 'image/png', - WEBP: 'image/webp', - JPEG: 'image/jpeg', - }, - FILE_TYPE_REGEX: { // Image MimeTypes allowed by iOS photos app. IMAGE: /\.(jpg|jpeg|png|webp|gif|tiff|bmp|heic|heif)$/, diff --git a/src/libs/cropOrRotateImage/getSaveFormat.ts b/src/libs/cropOrRotateImage/getSaveFormat.ts deleted file mode 100644 index 6d1ff280c5c5..000000000000 --- a/src/libs/cropOrRotateImage/getSaveFormat.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {SaveFormat} from 'expo-image-manipulator'; -import CONST from '@src/CONST'; - -function getSaveFormat(type: string) { - switch (type) { - case CONST.IMAGE_FILE_FORMAT.PNG: - return SaveFormat.PNG; - case CONST.IMAGE_FILE_FORMAT.WEBP: - return SaveFormat.WEBP; - case CONST.IMAGE_FILE_FORMAT.JPEG: - return SaveFormat.JPEG; - default: - return SaveFormat.JPEG; - } -} - -export default getSaveFormat; diff --git a/src/libs/cropOrRotateImage/index.native.ts b/src/libs/cropOrRotateImage/index.native.ts index 03c233450435..c3fed59ba4fa 100644 --- a/src/libs/cropOrRotateImage/index.native.ts +++ b/src/libs/cropOrRotateImage/index.native.ts @@ -1,6 +1,5 @@ -import {manipulateAsync} from 'expo-image-manipulator'; +import RNImageManipulator from '@oguzhnatly/react-native-image-manipulator'; import RNFetchBlob from 'react-native-blob-util'; -import getSaveFormat from './getSaveFormat'; import type {CropOrRotateImage} from './types'; /** @@ -8,8 +7,7 @@ import type {CropOrRotateImage} from './types'; */ const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => new Promise((resolve) => { - const format = getSaveFormat(options.type); - manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => { + RNImageManipulator.manipulate(uri, actions, options).then((result) => { RNFetchBlob.fs.stat(result.uri.replace('file://', '')).then(({size}) => { resolve({ ...result, diff --git a/src/libs/cropOrRotateImage/index.ts b/src/libs/cropOrRotateImage/index.ts index 1b669999a1ee..ea3d51a465ec 100644 --- a/src/libs/cropOrRotateImage/index.ts +++ b/src/libs/cropOrRotateImage/index.ts @@ -1,19 +1,127 @@ -import {manipulateAsync} from 'expo-image-manipulator'; -import getSaveFormat from './getSaveFormat'; -import type {CropOrRotateImage} from './types'; +import type {CropOptions, CropOrRotateImage, CropOrRotateImageOptions} from './types'; -const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => - new Promise((resolve) => { - const format = getSaveFormat(options.type); - manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => { - fetch(result.uri) - .then((res) => res.blob()) - .then((blob) => { - const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); - file.uri = URL.createObjectURL(file); - resolve(file); - }); +type SizeFromAngle = { + width: number; + height: number; +}; + +/** + * Calculates a size of canvas after rotation + */ +function sizeFromAngle(width: number, height: number, angle: number): SizeFromAngle { + const radians = (angle * Math.PI) / 180; + let sine = Math.cos(radians); + let cosine = Math.sin(radians); + if (cosine < 0) { + cosine = -cosine; + } + if (sine < 0) { + sine = -sine; + } + return {width: height * cosine + width * sine, height: height * sine + width * cosine}; +} + +/** + * Creates a new rotated canvas + */ +function rotateCanvas(canvas: HTMLCanvasElement, degrees: number): HTMLCanvasElement { + const {width, height} = sizeFromAngle(canvas.width, canvas.height, degrees); + + // We have to create a new canvas because it is not possible to change already drawn + // elements. Transformations such as rotation have to be applied before drawing + const result = document.createElement('canvas'); + result.width = width; + result.height = height; + + const context = result.getContext('2d'); + if (context) { + // In order to rotate image along its center we have to apply next transformation + context.translate(result.width / 2, result.height / 2); + + const radians = (degrees * Math.PI) / 180; + context.rotate(radians); + + context.drawImage(canvas, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); + } + return result; +} + +/** + * Creates new cropped canvas and returns it + */ +function cropCanvas(canvas: HTMLCanvasElement, options: CropOptions) { + let {originX = 0, originY = 0, width = 0, height = 0} = options; + const clamp = (value: number, max: number) => Math.max(0, Math.min(max, value)); + + width = clamp(width, canvas.width); + height = clamp(height, canvas.height); + originX = clamp(originX, canvas.width); + originY = clamp(originY, canvas.height); + + width = Math.min(originX + width, canvas.width) - originX; + height = Math.min(originY + height, canvas.height) - originY; + + const result = document.createElement('canvas'); + result.width = width; + result.height = height; + + const context = result.getContext('2d'); + context?.drawImage(canvas, originX, originY, width, height, 0, 0, width, height); + + return result; +} + +function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateImageOptions): Promise { + return new Promise((resolve) => { + canvas.toBlob((blob) => { + if (!blob) { + return; + } + const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); + file.uri = URL.createObjectURL(file); + resolve(file); }); }); +} + +/** + * Loads image from specified url + */ +function loadImageAsync(uri: string): Promise { + return new Promise((resolve, reject) => { + const imageSource = new Image(); + imageSource.crossOrigin = 'anonymous'; + const canvas = document.createElement('canvas'); + imageSource.onload = () => { + canvas.width = imageSource.naturalWidth; + canvas.height = imageSource.naturalHeight; + + const context = canvas.getContext('2d'); + context?.drawImage(imageSource, 0, 0, imageSource.naturalWidth, imageSource.naturalHeight); + + resolve(canvas); + }; + imageSource.onerror = () => reject(canvas); + imageSource.src = uri; + }); +} + +/** + * Crops and rotates the image on web + */ + +const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => + loadImageAsync(uri).then((originalCanvas) => { + const resultCanvas = actions.reduce((canvas, action) => { + if (action.crop) { + return cropCanvas(canvas, action.crop); + } + if (action.rotate) { + return rotateCanvas(canvas, action.rotate); + } + return canvas; + }, originalCanvas); + return convertCanvasToFile(resultCanvas, options); + }); export default cropOrRotateImage; diff --git a/src/libs/cropOrRotateImage/types.ts b/src/libs/cropOrRotateImage/types.ts index 9d9b9ff98080..f882e4f9bea2 100644 --- a/src/libs/cropOrRotateImage/types.ts +++ b/src/libs/cropOrRotateImage/types.ts @@ -1,4 +1,4 @@ -import type {ImageResult} from 'expo-image-manipulator'; +import type {RNImageManipulatorResult} from '@oguzhnatly/react-native-image-manipulator'; type CropOrRotateImageOptions = { type: string; @@ -6,21 +6,20 @@ type CropOrRotateImageOptions = { compress: number; }; -type CropAction = { - crop: { - originX: number; - originY: number; - width: number; - height: number; - }; +type CropOptions = { + originX: number; + originY: number; + width: number; + height: number; }; -type RotateOption = {rotate: number}; - -type Action = CropAction | RotateOption; +type Action = { + crop?: CropOptions; + rotate?: number; +}; -type CustomRNImageManipulatorResult = ImageResult & {size: number; type: string; name: string}; +type CustomRNImageManipulatorResult = RNImageManipulatorResult & {size: number; type: string; name: string}; type CropOrRotateImage = (uri: string, actions: Action[], options: CropOrRotateImageOptions) => Promise; -export type {CustomRNImageManipulatorResult, CropOrRotateImage}; +export type {CropOrRotateImage, CropOptions, Action, CropOrRotateImageOptions, CustomRNImageManipulatorResult}; From 37f1c3ad8ea1014bc8cbdeafac42720f9e935120 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Sun, 10 Mar 2024 17:41:42 +0000 Subject: [PATCH 7/7] Update version to 1.4.49-4 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 812f1008777d..f714ed005740 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001044903 - versionName "1.4.49-3" + versionCode 1001044904 + versionName "1.4.49-4" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 602cb1de71ba..712909df6b61 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.49.3 + 1.4.49.4 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 61ac30356852..b1a4aa336ab8 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.49.3 + 1.4.49.4 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 8b48846258a3..c90bc159062b 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.49 CFBundleVersion - 1.4.49.3 + 1.4.49.4 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 133556e5de8c..81686286bf1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.49-3", + "version": "1.4.49-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.49-3", + "version": "1.4.49-4", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1c0afbb5417b..7dab1aeaa386 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.49-3", + "version": "1.4.49-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",