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/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 31e22491e2b9..bb1766f40e1f 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -241,9 +241,6 @@ const ONYXKEYS = {
// This can be either "light", "dark" or "system"
PREFERRED_THEME: 'preferredTheme',
- // Experimental memory only Onyx mode flag
- IS_USING_MEMORY_ONLY_KEYS: 'isUsingMemoryOnlyKeys',
-
// Information about the onyx updates IDs that were received from the server
ONYX_UPDATES_FROM_SERVER: 'onyxUpdatesFromServer',
@@ -566,7 +563,6 @@ type OnyxValuesMapping = {
[ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record;
[ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID]: string;
[ONYXKEYS.PREFERRED_THEME]: ValueOf;
- [ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS]: boolean;
[ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken;
[ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.OnyxUpdatesFromServer;
[ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number;
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/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts
index 3b5cd60d84a4..ffe4f8a9bea9 100644
--- a/src/libs/Middleware/SaveResponseInOnyx.ts
+++ b/src/libs/Middleware/SaveResponseInOnyx.ts
@@ -1,8 +1,6 @@
import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
-import * as MemoryOnlyKeys from '@userActions/MemoryOnlyKeys/MemoryOnlyKeys';
import * as OnyxUpdates from '@userActions/OnyxUpdates';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
import type Middleware from './types';
// If we're executing any of these requests, we don't need to trigger our OnyxUpdates flow to update the current data even if our current value is out of
@@ -19,16 +17,6 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) =>
return Promise.resolve(response);
}
- // If there is an OnyxUpdate for using memory only keys, enable them
- onyxUpdates?.find(({key, value}) => {
- if (key !== ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS || !value) {
- return false;
- }
-
- MemoryOnlyKeys.enable();
- return true;
- });
-
const responseToApply = {
type: CONST.ONYX_UPDATE_TYPES.HTTPS,
lastUpdateID: Number(response?.lastUpdateID ?? 0),
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 8c582b8ab259..a986e2995156 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -49,9 +49,6 @@ type AuthScreensProps = {
/** The report ID of the last opened public room as anonymous user */
lastOpenedPublicRoomID: OnyxEntry;
- /** Opt-in experimental mode that prevents certain Onyx keys from persisting to disk */
- isUsingMemoryOnlyKeys: OnyxEntry;
-
/** The last Onyx update ID was applied to the client */
initialLastUpdateIDAppliedToClient: OnyxEntry;
};
@@ -150,7 +147,7 @@ const modalScreenListeners = {
},
};
-function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = false, initialLastUpdateIDAppliedToClient}: AuthScreensProps) {
+function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDAppliedToClient}: AuthScreensProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -168,7 +165,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = f
const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT;
const currentUrl = getCurrentUrl();
const isLoggingInAsNewUser = !!session?.email && SessionUtils.isLoggingInAsNewUser(currentUrl, session.email);
- const shouldGetAllData = !!isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession();
// Sign out the current user if we're transitioning with a different user
const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS);
const isSupportalTransition = currentUrl.includes('authTokenType=support');
@@ -190,10 +186,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = f
// If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app
// or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp().
- // Note: If a Guide has enabled the memory only key mode then we do want to run OpenApp as their app will not be rehydrated with
- // the correct state on refresh. They are explicitly opting out of storing data they would need (i.e. reports_) to take advantage of
- // the optimizations performed during ReconnectApp.
- if (shouldGetAllData) {
+ if (SessionUtils.didUserLogInDuringSession()) {
App.openApp();
} else {
App.reconnectApp(initialLastUpdateIDAppliedToClient);
@@ -392,9 +385,6 @@ export default withOnyx({
lastOpenedPublicRoomID: {
key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID,
},
- isUsingMemoryOnlyKeys: {
- key: ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS,
- },
initialLastUpdateIDAppliedToClient: {
key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT,
},
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 9f5b0ada4cb1..4689fd03ebd0 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -102,11 +102,6 @@ function isExpensifyTeam(email: string | undefined): boolean {
return emailDomain === CONST.EXPENSIFY_PARTNER_NAME || emailDomain === CONST.EMAIL.GUIDES_DOMAIN;
}
-function isExpensifyGuideTeam(email: string): boolean {
- const emailDomain = Str.extractEmailDomain(email ?? '');
- return emailDomain === CONST.EMAIL.GUIDES_DOMAIN;
-}
-
/**
* Checks if the current user is an admin of the policy.
*/
@@ -282,7 +277,6 @@ export {
getPolicyBrickRoadIndicatorStatus,
shouldShowPolicy,
isExpensifyTeam,
- isExpensifyGuideTeam,
isInstantSubmitEnabled,
isFreeGroupPolicy,
isPolicyAdmin,
diff --git a/src/libs/actions/MemoryOnlyKeys/MemoryOnlyKeys.ts b/src/libs/actions/MemoryOnlyKeys/MemoryOnlyKeys.ts
deleted file mode 100644
index 15b9133f0aaf..000000000000
--- a/src/libs/actions/MemoryOnlyKeys/MemoryOnlyKeys.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import Onyx from 'react-native-onyx';
-import type {OnyxKey} from 'react-native-onyx';
-import Log from '@libs/Log';
-import ONYXKEYS from '@src/ONYXKEYS';
-
-const memoryOnlyKeys: OnyxKey[] = [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.POLICY, ONYXKEYS.PERSONAL_DETAILS_LIST];
-
-const enable = () => {
- Log.info('[MemoryOnlyKeys] enabled');
- Onyx.set(ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS, true);
- Onyx.setMemoryOnlyKeys(memoryOnlyKeys);
-};
-
-const disable = () => {
- Log.info('[MemoryOnlyKeys] disabled');
- Onyx.set(ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS, false);
- Onyx.setMemoryOnlyKeys([]);
-};
-
-export {disable, enable};
diff --git a/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/index.native.ts b/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/index.native.ts
deleted file mode 100644
index b89e03bdefdc..000000000000
--- a/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/index.native.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type ExposeGlobalMemoryOnlyKeysMethods from './types';
-
-/**
- * This is a no-op because the global methods will only work for web and desktop
- */
-const exposeGlobalMemoryOnlyKeysMethods: ExposeGlobalMemoryOnlyKeysMethods = () => {};
-
-export default exposeGlobalMemoryOnlyKeysMethods;
diff --git a/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/index.ts b/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/index.ts
deleted file mode 100644
index 4514edacb288..000000000000
--- a/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as MemoryOnlyKeys from '@userActions/MemoryOnlyKeys/MemoryOnlyKeys';
-import type ExposeGlobalMemoryOnlyKeysMethods from './types';
-
-const exposeGlobalMemoryOnlyKeysMethods: ExposeGlobalMemoryOnlyKeysMethods = () => {
- window.enableMemoryOnlyKeys = () => {
- MemoryOnlyKeys.enable();
- };
- window.disableMemoryOnlyKeys = () => {
- MemoryOnlyKeys.disable();
- };
-};
-
-export default exposeGlobalMemoryOnlyKeysMethods;
diff --git a/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/types.ts b/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/types.ts
deleted file mode 100644
index 4cb50041b627..000000000000
--- a/src/libs/actions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods/types.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-type ExposeGlobalMemoryOnlyKeysMethods = () => void;
-
-export default ExposeGlobalMemoryOnlyKeysMethods;
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);
diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx
index bca0fbd2f8ef..4286a2603341 100644
--- a/src/pages/signin/LoginForm/BaseLoginForm.tsx
+++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx
@@ -22,15 +22,12 @@ import useThemeStyles from '@hooks/useThemeStyles';
import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
import * as ErrorUtils from '@libs/ErrorUtils';
import isInputAutoFilled from '@libs/isInputAutoFilled';
-import Log from '@libs/Log';
import * as LoginUtils from '@libs/LoginUtils';
import {parsePhoneNumber} from '@libs/PhoneNumber';
-import * as PolicyUtils from '@libs/PolicyUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside';
import * as CloseAccount from '@userActions/CloseAccount';
-import * as MemoryOnlyKeys from '@userActions/MemoryOnlyKeys/MemoryOnlyKeys';
import * as Session from '@userActions/Session';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
@@ -151,12 +148,6 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false
const loginTrim = login.trim();
- // If the user has entered a guide email, then we are going to enable an experimental Onyx mode to help with performance
- if (PolicyUtils.isExpensifyGuideTeam(loginTrim)) {
- Log.info('Detected guide email in login field, setting memory only keys.');
- MemoryOnlyKeys.enable();
- }
-
const phoneLogin = LoginUtils.appendCountryCode(LoginUtils.getPhoneNumberWithoutSpecialChars(loginTrim));
const parsedPhoneNumber = parsePhoneNumber(phoneLogin);
diff --git a/src/setup/index.ts b/src/setup/index.ts
index a97862df9ae7..f79e6a461a3e 100644
--- a/src/setup/index.ts
+++ b/src/setup/index.ts
@@ -3,7 +3,6 @@ import Onyx from 'react-native-onyx';
import intlPolyfill from '@libs/IntlPolyfill';
import * as Metrics from '@libs/Metrics';
import * as Device from '@userActions/Device';
-import exposeGlobalMemoryOnlyKeysMethods from '@userActions/MemoryOnlyKeys/exposeGlobalMemoryOnlyKeysMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import addUtilsToWindow from './addUtilsToWindow';
@@ -47,8 +46,6 @@ export default function () {
},
});
- exposeGlobalMemoryOnlyKeysMethods();
-
Device.setDeviceID();
// Force app layout to work left to right because our design does not currently support devices using this mode
diff --git a/src/types/modules/react-native-onyx.d.ts b/src/types/modules/react-native-onyx.d.ts
index ade71706a8e8..8498b03ec933 100644
--- a/src/types/modules/react-native-onyx.d.ts
+++ b/src/types/modules/react-native-onyx.d.ts
@@ -14,8 +14,6 @@ declare global {
// Global methods for Onyx key management for debugging purposes
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Window {
- enableMemoryOnlyKeys: () => void;
- disableMemoryOnlyKeys: () => void;
Onyx: typeof Onyx;
}
}