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/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..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": {
@@ -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..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.",
@@ -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/ROUTES.ts b/src/ROUTES.ts
index db0068365760..856a6fb89a3e 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}${reportActionID ? `/${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 68114dcf4e4c..51a9148a3e6a 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -799,6 +799,7 @@ function MoneyRequestConfirmationList(props) {
props.transaction.transactionID,
props.reportID,
Navigation.getActiveRouteWithoutParams(),
+ props.reportActionID,
),
);
return;
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};
diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js
index 1b53dab12fa3..c1d1577a5d62 100644
--- a/src/pages/iou/request/step/IOURequestStepTag.js
+++ b/src/pages/iou/request/step/IOURequestStepTag.js
@@ -48,7 +48,16 @@ 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({
+ /** Currently logged in user accountID */
+ accountID: PropTypes.number,
+
+ /** Currently logged in user email */
+ email: PropTypes.string,
+ }).isRequired,
};
const defaultProps = {
@@ -57,7 +66,7 @@ const defaultProps = {
policyTags: null,
policyCategories: null,
transaction: {},
- parentReportActions: {},
+ reportActions: {},
};
function IOURequestStepTag({
@@ -66,10 +75,11 @@ function IOURequestStepTag({
policyTags,
report,
route: {
- params: {action, tagIndex: rawTagIndex, transactionID, backTo, iouType},
+ params: {action, tagIndex: rawTagIndex, transactionID, backTo, iouType, reportActionID},
},
transaction,
- parentReportActions,
+ reportActions,
+ session,
}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -80,12 +90,14 @@ function IOURequestStepTag({
const tag = TransactionUtils.getTag(transaction, tagIndex);
const isEditing = action === CONST.IOU.ACTION.EDIT;
const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
+ const reportAction = reportActions[report.parentReportActionID || reportActionID];
+ const canEditSplitBill = isSplitBill && reportAction && session.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction);
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);
- const parentReportAction = parentReportActions[report.parentReportActionID];
+
const shouldShowTag = ReportUtils.isGroupPolicy(report) && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists));
// eslint-disable-next-line rulesdir/no-negated-variables
- const shouldShowNotFoundPage = !shouldShowTag || (isEditing && !canEditMoneyRequest(parentReportAction));
+ const shouldShowNotFoundPage = !shouldShowTag || (isEditing && (isSplitBill ? !canEditSplitBill : !canEditMoneyRequest(reportAction)));
const navigateBack = () => {
Navigation.goBack(backTo);
@@ -154,9 +166,23 @@ export default compose(
policyTags: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
},
- parentReportActions: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`,
+ reportActions: {
+ 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: {
+ key: ONYXKEYS.SESSION,
+ },
}),
)(IOURequestStepTag);