diff --git a/android/app/build.gradle b/android/app/build.gradle
index 7ea2f7976e66..d78c4b107e84 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -107,8 +107,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001047605
- versionName "1.4.76-5"
+ versionCode 1001047700
+ versionName "1.4.77-0"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/assets/images/receipt-plus.svg b/assets/images/receipt-plus.svg
new file mode 100644
index 000000000000..3907da65c472
--- /dev/null
+++ b/assets/images/receipt-plus.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/desktop/ELECTRON_EVENTS.ts b/desktop/ELECTRON_EVENTS.ts
index 607ad7b21580..b06794567c7d 100644
--- a/desktop/ELECTRON_EVENTS.ts
+++ b/desktop/ELECTRON_EVENTS.ts
@@ -9,6 +9,10 @@ const ELECTRON_EVENTS = {
KEYBOARD_SHORTCUTS_PAGE: 'keyboard-shortcuts-page',
START_UPDATE: 'start-update',
UPDATE_DOWNLOADED: 'update-downloaded',
+ DOWNLOAD: 'download',
+ DOWNLOAD_COMPLETED: 'download-completed',
+ DOWNLOAD_FAILED: 'download-started',
+ DOWNLOAD_CANCELED: 'download-canceled',
SILENT_UPDATE: 'silent-update',
} as const;
diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts
index 61ede178da2d..74b91c4634a1 100644
--- a/desktop/contextBridge.ts
+++ b/desktop/contextBridge.ts
@@ -16,10 +16,19 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [
ELECTRON_EVENTS.REQUEST_VISIBILITY,
ELECTRON_EVENTS.START_UPDATE,
ELECTRON_EVENTS.LOCALE_UPDATED,
+ ELECTRON_EVENTS.DOWNLOAD,
ELECTRON_EVENTS.SILENT_UPDATE,
] as const;
-const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR] as const;
+const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [
+ ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE,
+ ELECTRON_EVENTS.UPDATE_DOWNLOADED,
+ ELECTRON_EVENTS.FOCUS,
+ ELECTRON_EVENTS.BLUR,
+ ELECTRON_EVENTS.DOWNLOAD_COMPLETED,
+ ELECTRON_EVENTS.DOWNLOAD_FAILED,
+ ELECTRON_EVENTS.DOWNLOAD_CANCELED,
+] as const;
const getErrorMessage = (channel: string): string => `Electron context bridge cannot be used with channel '${channel}'`;
diff --git a/desktop/createDownloadQueue.ts b/desktop/createDownloadQueue.ts
new file mode 100644
index 000000000000..132848c5da9e
--- /dev/null
+++ b/desktop/createDownloadQueue.ts
@@ -0,0 +1,116 @@
+import type {BrowserWindow} from 'electron';
+import {app} from 'electron';
+import * as path from 'path';
+import createQueue from '@libs/Queue/Queue';
+import CONST from '@src/CONST';
+import ELECTRON_EVENTS from './ELECTRON_EVENTS';
+import type Options from './electronDownloadManagerType';
+
+type DownloadItem = {
+ // The window where the download will be initiated
+ win: BrowserWindow;
+
+ // The URL of the file to be downloaded
+ url: string;
+
+ // The options for the download, such as save path, file name, etc.
+ options: Options;
+};
+
+/**
+ * Returns the filename with extension based on the given name and MIME type.
+ * @param name - The name of the file.
+ * @param mime - The MIME type of the file.
+ * @returns The filename with extension.
+ */
+const getFilenameFromMime = (name: string, mime: string): string => {
+ const extensions = mime.split('/').pop();
+ return `${name}.${extensions}`;
+};
+
+const createDownloadQueue = () => {
+ const downloadItemProcessor = (item: DownloadItem): Promise =>
+ new Promise((resolve, reject) => {
+ let downloadTimeout: NodeJS.Timeout;
+ let downloadListener: (event: Electron.Event, electronDownloadItem: Electron.DownloadItem) => void;
+
+ const timeoutFunction = () => {
+ item.win.webContents.session.removeListener('will-download', downloadListener);
+ resolve();
+ };
+
+ const listenerFunction = (event: Electron.Event, electronDownloadItem: Electron.DownloadItem) => {
+ clearTimeout(downloadTimeout);
+
+ const options = item.options;
+ const cleanup = () => item.win.webContents.session.removeListener('will-download', listenerFunction);
+ const errorMessage = `The download of ${electronDownloadItem.getFilename()} was interrupted`;
+
+ if (options.directory && !path.isAbsolute(options.directory)) {
+ throw new Error('The `directory` option must be an absolute path');
+ }
+
+ const directory = options.directory ?? app.getPath('downloads');
+
+ let filePath: string;
+ if (options.filename) {
+ filePath = path.join(directory, options.filename);
+ } else {
+ const filename = electronDownloadItem.getFilename();
+ const name = path.extname(filename) ? filename : getFilenameFromMime(filename, electronDownloadItem.getMimeType());
+
+ filePath = options.overwrite ? path.join(directory, name) : path.join(directory, name);
+ }
+
+ if (options.saveAs) {
+ electronDownloadItem.setSaveDialogOptions({defaultPath: filePath, ...options.dialogOptions});
+ } else {
+ electronDownloadItem.setSavePath(filePath);
+ }
+
+ electronDownloadItem.on('updated', (_, state) => {
+ if (state !== 'interrupted') {
+ return;
+ }
+
+ item.win.webContents.send(ELECTRON_EVENTS.DOWNLOAD_CANCELED, {url: item.url});
+ cleanup();
+ reject(new Error(errorMessage));
+ electronDownloadItem.cancel();
+ });
+
+ electronDownloadItem.on('done', (_, state) => {
+ cleanup();
+ if (state === 'cancelled') {
+ item.win.webContents.send(ELECTRON_EVENTS.DOWNLOAD_CANCELED, {url: item.url});
+ resolve();
+ } else if (state === 'interrupted') {
+ item.win.webContents.send(ELECTRON_EVENTS.DOWNLOAD_FAILED, {url: item.url});
+ reject(new Error(errorMessage));
+ } else if (state === 'completed') {
+ if (process.platform === 'darwin') {
+ const savePath = electronDownloadItem.getSavePath();
+ app.dock.downloadFinished(savePath);
+ }
+ item.win.webContents.send(ELECTRON_EVENTS.DOWNLOAD_COMPLETED, {url: item.url});
+ resolve();
+ }
+ });
+ };
+
+ downloadTimeout = setTimeout(timeoutFunction, CONST.DOWNLOADS_TIMEOUT);
+ downloadListener = listenerFunction;
+
+ item.win.webContents.downloadURL(item.url);
+ item.win.webContents.session.on('will-download', downloadListener);
+ });
+
+ const queue = createQueue(downloadItemProcessor);
+
+ const enqueueDownloadItem = (item: DownloadItem): void => {
+ queue.enqueue(item);
+ };
+ return {enqueueDownloadItem, dequeueDownloadItem: queue.dequeue};
+};
+
+export default createDownloadQueue;
diff --git a/desktop/electronDownloadManagerType.ts b/desktop/electronDownloadManagerType.ts
new file mode 100644
index 000000000000..755efe173887
--- /dev/null
+++ b/desktop/electronDownloadManagerType.ts
@@ -0,0 +1,49 @@
+import type {SaveDialogOptions} from 'electron';
+
+type Options = {
+ /**
+ Show a `Save As…` dialog instead of downloading immediately.
+
+ Note: Only use this option when strictly necessary. Downloading directly without a prompt is a much better user experience.
+
+ @default false
+ */
+ readonly saveAs?: boolean;
+
+ /**
+ The directory to save the file in.
+
+ Must be an absolute path.
+
+ Default: [User's downloads directory](https://electronjs.org/docs/api/app/#appgetpathname)
+ */
+ readonly directory?: string;
+
+ /**
+ Name of the saved file.
+ This option only makes sense for `electronDownloadManager.download()`.
+
+ Default: [`downloadItem.getFilename()`](https://electronjs.org/docs/api/download-item/#downloaditemgetfilename)
+ */
+ readonly filename?: string;
+
+ /**
+ Allow downloaded files to overwrite files with the same name in the directory they are saved to.
+
+ The default behavior is to append a number to the filename.
+
+ @default false
+ */
+ readonly overwrite?: boolean;
+
+ /**
+ Customize the save dialog.
+
+ If `defaultPath` is not explicity defined, a default value is assigned based on the file path.
+
+ @default {}
+ */
+ readonly dialogOptions?: SaveDialogOptions;
+};
+
+export default Options;
diff --git a/desktop/main.ts b/desktop/main.ts
index 64587f42bf56..0f4774d3b73b 100644
--- a/desktop/main.ts
+++ b/desktop/main.ts
@@ -15,6 +15,8 @@ import type PlatformSpecificUpdater from '@src/setup/platformSetup/types';
import type {Locale} from '@src/types/onyx';
import ELECTRON_EVENTS from './ELECTRON_EVENTS';
+const createDownloadQueue = require('./createDownloadQueue').default;
+
const port = process.env.PORT ?? 8082;
const {DESKTOP_SHORTCUT_ACCELERATOR, LOCALES} = CONST;
@@ -613,6 +615,15 @@ const mainWindow = (): Promise => {
}
});
+ const downloadQueue = createDownloadQueue();
+ ipcMain.on(ELECTRON_EVENTS.DOWNLOAD, (event, downloadData) => {
+ const downloadItem = {
+ ...downloadData,
+ win: browserWindow,
+ };
+ downloadQueue.enqueueDownloadItem(downloadItem);
+ });
+
// Automatically check for and install the latest version in the background
ipcMain.on(ELECTRON_EVENTS.SILENT_UPDATE, () => {
if (isSilentUpdating) {
diff --git a/docs/redirects.csv b/docs/redirects.csv
index 4867ee4f8f4b..5e4d06619653 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -175,4 +175,5 @@ https://help.expensify.com/articles/new-expensify/expenses/Referral-Program.html
https://help.expensify.com/articles/new-expensify/workspaces/The-Free-Plan,https://help.expensify.com/new-expensify/hubs/workspaces/
https://help.expensify.com/new-expensify/hubs/expenses/Connect-a-Bank-Account,https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account
https://help.expensify.com/articles/new-expensify/settings/Security,https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security
-https://help.expensify.com/articles/expensify-classic/workspaces/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency
\ No newline at end of file
+https://help.expensify.com/articles/expensify-classic/workspaces/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency
+https://help.expensify.com/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account.html,https://help.expensify.com/articles/new-expensify/expenses/Connect-a-Business-Bank-Account
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index bcd60a84faf3..9c95f45f3ddd 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.76
+ 1.4.77
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.76.5
+ 1.4.77.0
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index bbbde217f592..e88964613a93 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.76
+ 1.4.77
CFBundleSignature
????
CFBundleVersion
- 1.4.76.5
+ 1.4.77.0
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 3d36be063ccc..311360dd247f 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 1.4.76
+ 1.4.77
CFBundleVersion
- 1.4.76.5
+ 1.4.77.0
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 8b8907c34f67..3275ee64d44f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.76-5",
+ "version": "1.4.77-0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.76-5",
+ "version": "1.4.77-0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -59,7 +59,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1713f28214f0e7176c4fd13433fb0ea15491ebf9",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#18fa764be9d68f72b48d238dcc20f2b0ca8f1147",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
@@ -103,7 +103,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "2.0.41",
+ "react-native-onyx": "2.0.32",
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -241,7 +241,7 @@
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"type-fest": "^4.10.2",
- "typescript": "^5.4.5",
+ "typescript": "^5.3.2",
"wait-port": "^0.2.9",
"webpack": "^5.76.0",
"webpack-bundle-analyzer": "^4.5.0",
@@ -1855,19 +1855,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-transform-object-assign": {
- "version": "7.18.6",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
@@ -20355,8 +20342,8 @@
},
"node_modules/expensify-common": {
"version": "1.0.0",
- "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#1713f28214f0e7176c4fd13433fb0ea15491ebf9",
- "integrity": "sha512-uy1+axUTTuPKwAR06xNG/tGIJ+uaavmSQgKiNU7pQVR94ibNzDD2WESn2E7OEP9/QrHa61lfFlluTjFvvz5I8Q==",
+ "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#18fa764be9d68f72b48d238dcc20f2b0ca8f1147",
+ "integrity": "sha512-AbeXop0pAVnkOJ7uVShqF7q9xwOYADW1mit0kK73ADkNuuQuHCYTqQSsQDuLaG80c5N96h+NZF/9LvcrhU2aFw==",
"license": "MIT",
"dependencies": {
"classnames": "2.5.0",
@@ -31485,9 +31472,9 @@
}
},
"node_modules/react-native-onyx": {
- "version": "2.0.41",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.41.tgz",
- "integrity": "sha512-33r0sVBq7MV/GZwRneRt81uxgW8x3YG75VNJvThycB/dkCnGCfbxoVkZADVH3ET3jzfFXy9wnS06sZnZp78zMQ==",
+ "version": "2.0.32",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.32.tgz",
+ "integrity": "sha512-tB9wqMJGTLOYfrfplRP+9aq5JdD8w/hV/OZsMAVH+ewbE1zLY8OymUsAsIFdF1v+cB8HhehP569JVLZmhm6bsg==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -36088,10 +36075,9 @@
}
},
"node_modules/typescript": {
- "version": "5.4.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
- "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+ "version": "5.3.3",
"devOptional": true,
+ "license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index 917d9de06432..b3c7267abe73 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.76-5",
+ "version": "1.4.77-0",
"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.",
@@ -111,7 +111,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1713f28214f0e7176c4fd13433fb0ea15491ebf9",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#18fa764be9d68f72b48d238dcc20f2b0ca8f1147",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
@@ -155,7 +155,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "2.0.41",
+ "react-native-onyx": "2.0.32",
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -293,7 +293,7 @@
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"type-fest": "^4.10.2",
- "typescript": "^5.4.5",
+ "typescript": "^5.3.2",
"wait-port": "^0.2.9",
"webpack": "^5.76.0",
"webpack-bundle-analyzer": "^4.5.0",
diff --git a/patches/@shopify+flash-list+1.6.3.patch b/patches/@shopify+flash-list+1.6.3.patch
index ab347fbb4e9c..e3d690055ff8 100644
--- a/patches/@shopify+flash-list+1.6.3.patch
+++ b/patches/@shopify+flash-list+1.6.3.patch
@@ -867,7 +867,7 @@ index 023b94a..0000000
-{"program":{"fileNames":["../node_modules/typescript/lib/lib.es5.d.ts","../node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/typescript/lib/lib.es2016.d.ts","../node_modules/typescript/lib/lib.es2017.d.ts","../node_modules/typescript/lib/lib.es2018.d.ts","../node_modules/typescript/lib/lib.es2019.d.ts","../node_modules/typescript/lib/lib.es2020.d.ts","../node_modules/typescript/lib/lib.dom.d.ts","../node_modules/typescript/lib/lib.dom.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../node_modules/typescript/lib/lib.es2017.object.d.ts","../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2017.string.d.ts","../node_modules/typescript/lib/lib.es2017.intl.d.ts","../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../node_modules/typescript/lib/lib.es2018.intl.d.ts","../node_modules/typescript/lib/lib.es2018.promise.d.ts","../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../node_modules/typescript/lib/lib.es2019.array.d.ts","../node_modules/typescript/lib/lib.es2019.object.d.ts","../node_modules/typescript/lib/lib.es2019.string.d.ts","../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../node_modules/typescript/lib/lib.es2020.date.d.ts","../node_modules/typescript/lib/lib.es2020.promise.d.ts","../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2020.string.d.ts","../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.es2020.intl.d.ts","../node_modules/typescript/lib/lib.es2020.number.d.ts","../node_modules/typescript/lib/lib.esnext.intl.d.ts","../node_modules/tslib/tslib.d.ts","../node_modules/@types/react-native/modules/BatchedBridge.d.ts","../node_modules/@types/react-native/modules/Codegen.d.ts","../node_modules/@types/react-native/modules/Devtools.d.ts","../node_modules/@types/react-native/modules/globals.d.ts","../node_modules/@types/react-native/modules/LaunchScreen.d.ts","../node_modules/@types/react/global.d.ts","../node_modules/csstype/index.d.ts","../node_modules/@types/prop-types/index.d.ts","../node_modules/@types/scheduler/tracing.d.ts","../node_modules/@types/react/index.d.ts","../node_modules/@types/react-native/private/Utilities.d.ts","../node_modules/@types/react-native/public/Insets.d.ts","../node_modules/@types/react-native/Libraries/ReactNative/RendererProxy.d.ts","../node_modules/@types/react-native/public/ReactNativeTypes.d.ts","../node_modules/@types/react-native/Libraries/Types/CoreEventTypes.d.ts","../node_modules/@types/react-native/public/ReactNativeRenderer.d.ts","../node_modules/@types/react-native/Libraries/Components/Touchable/Touchable.d.ts","../node_modules/@types/react-native/Libraries/Components/View/ViewAccessibility.d.ts","../node_modules/@types/react-native/Libraries/Components/View/ViewPropTypes.d.ts","../node_modules/@types/react-native/Libraries/Components/RefreshControl/RefreshControl.d.ts","../node_modules/@types/react-native/Libraries/Components/ScrollView/ScrollView.d.ts","../node_modules/@types/react-native/Libraries/Components/View/View.d.ts","../node_modules/@types/react-native/Libraries/Image/ImageResizeMode.d.ts","../node_modules/@types/react-native/Libraries/Image/ImageSource.d.ts","../node_modules/@types/react-native/Libraries/Image/Image.d.ts","../node_modules/@react-native/virtualized-lists/Lists/VirtualizedList.d.ts","../node_modules/@react-native/virtualized-lists/index.d.ts","../node_modules/@types/react-native/Libraries/Lists/FlatList.d.ts","../node_modules/@types/react-native/Libraries/Lists/SectionList.d.ts","../node_modules/@types/react-native/Libraries/Text/Text.d.ts","../node_modules/@types/react-native/Libraries/Animated/Animated.d.ts","../node_modules/@types/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts","../node_modules/@types/react-native/Libraries/StyleSheet/StyleSheet.d.ts","../node_modules/@types/react-native/Libraries/StyleSheet/processColor.d.ts","../node_modules/@types/react-native/Libraries/ActionSheetIOS/ActionSheetIOS.d.ts","../node_modules/@types/react-native/Libraries/Alert/Alert.d.ts","../node_modules/@types/react-native/Libraries/Animated/Easing.d.ts","../node_modules/@types/react-native/Libraries/Animated/useAnimatedValue.d.ts","../node_modules/@types/react-native/Libraries/vendor/emitter/EventEmitter.d.ts","../node_modules/@types/react-native/Libraries/EventEmitter/RCTDeviceEventEmitter.d.ts","../node_modules/@types/react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter.d.ts","../node_modules/@types/react-native/Libraries/AppState/AppState.d.ts","../node_modules/@types/react-native/Libraries/BatchedBridge/NativeModules.d.ts","../node_modules/@types/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts","../node_modules/@types/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.d.ts","../node_modules/@types/react-native/Libraries/Components/Clipboard/Clipboard.d.ts","../node_modules/@types/react-native/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.d.ts","../node_modules/@types/react-native/Libraries/EventEmitter/NativeEventEmitter.d.ts","../node_modules/@types/react-native/Libraries/Components/Keyboard/Keyboard.d.ts","../node_modules/@types/react-native/private/TimerMixin.d.ts","../node_modules/@types/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.d.ts","../node_modules/@types/react-native/Libraries/Components/Pressable/Pressable.d.ts","../node_modules/@types/react-native/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.d.ts","../node_modules/@types/react-native/Libraries/Components/SafeAreaView/SafeAreaView.d.ts","../node_modules/@types/react-native/Libraries/Components/StatusBar/StatusBar.d.ts","../node_modules/@types/react-native/Libraries/Components/Switch/Switch.d.ts","../node_modules/@types/react-native/Libraries/Components/TextInput/InputAccessoryView.d.ts","../node_modules/@types/react-native/Libraries/Components/TextInput/TextInput.d.ts","../node_modules/@types/react-native/Libraries/Components/ToastAndroid/ToastAndroid.d.ts","../node_modules/@types/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.d.ts","../node_modules/@types/react-native/Libraries/Components/Touchable/TouchableHighlight.d.ts","../node_modules/@types/react-native/Libraries/Components/Touchable/TouchableOpacity.d.ts","../node_modules/@types/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.d.ts","../node_modules/@types/react-native/Libraries/Components/Button.d.ts","../node_modules/@types/react-native/Libraries/DevToolsSettings/DevToolsSettingsManager.d.ts","../node_modules/@types/react-native/Libraries/Interaction/InteractionManager.d.ts","../node_modules/@types/react-native/Libraries/Interaction/PanResponder.d.ts","../node_modules/@types/react-native/Libraries/LayoutAnimation/LayoutAnimation.d.ts","../node_modules/@types/react-native/Libraries/Linking/Linking.d.ts","../node_modules/@types/react-native/Libraries/LogBox/LogBox.d.ts","../node_modules/@types/react-native/Libraries/Modal/Modal.d.ts","../node_modules/@types/react-native/Libraries/Performance/Systrace.d.ts","../node_modules/@types/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.d.ts","../node_modules/@types/react-native/Libraries/PushNotificationIOS/PushNotificationIOS.d.ts","../node_modules/@types/react-native/Libraries/Utilities/IPerformanceLogger.d.ts","../node_modules/@types/react-native/Libraries/ReactNative/AppRegistry.d.ts","../node_modules/@types/react-native/Libraries/ReactNative/I18nManager.d.ts","../node_modules/@types/react-native/Libraries/ReactNative/RootTag.d.ts","../node_modules/@types/react-native/Libraries/ReactNative/UIManager.d.ts","../node_modules/@types/react-native/Libraries/ReactNative/requireNativeComponent.d.ts","../node_modules/@types/react-native/Libraries/Settings/Settings.d.ts","../node_modules/@types/react-native/Libraries/Share/Share.d.ts","../node_modules/@types/react-native/Libraries/StyleSheet/PlatformColorValueTypesIOS.d.ts","../node_modules/@types/react-native/Libraries/StyleSheet/PlatformColorValueTypes.d.ts","../node_modules/@types/react-native/Libraries/TurboModule/RCTExport.d.ts","../node_modules/@types/react-native/Libraries/TurboModule/TurboModuleRegistry.d.ts","../node_modules/@types/react-native/Libraries/Utilities/Appearance.d.ts","../node_modules/@types/react-native/Libraries/Utilities/BackHandler.d.ts","../node_modules/@types/react-native/Libraries/Utilities/DevSettings.d.ts","../node_modules/@types/react-native/Libraries/Utilities/Dimensions.d.ts","../node_modules/@types/react-native/Libraries/Utilities/PixelRatio.d.ts","../node_modules/@types/react-native/Libraries/Utilities/Platform.d.ts","../node_modules/@types/react-native/Libraries/Vibration/Vibration.d.ts","../node_modules/@types/react-native/Libraries/YellowBox/YellowBoxDeprecated.d.ts","../node_modules/@types/react-native/Libraries/vendor/core/ErrorUtils.d.ts","../node_modules/@types/react-native/public/DeprecatedPropertiesAlias.d.ts","../node_modules/@types/react-native/index.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/dependencies/ContextProvider.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/dependencies/DataProvider.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/layoutmanager/LayoutManager.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/dependencies/LayoutProvider.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/dependencies/GridLayoutProvider.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/scrollcomponent/BaseScrollView.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/ViewabilityTracker.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/VirtualRenderer.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/ItemAnimator.d.ts","../node_modules/recyclerlistview/dist/reactnative/utils/ComponentCompat.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/RecyclerListView.d.ts","../node_modules/recyclerlistview/dist/reactnative/utils/AutoScroll.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/layoutmanager/GridLayoutManager.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/ProgressiveListView.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/devutils/debughandlers/resize/ResizeDebugHandler.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/devutils/debughandlers/DebugHandlers.d.ts","../node_modules/recyclerlistview/dist/reactnative/index.d.ts","../node_modules/recyclerlistview/dist/reactnative/core/StickyContainer.d.ts","../node_modules/recyclerlistview/sticky/index.d.ts","../src/native/auto-layout/AutoLayoutViewNativeComponentProps.ts","../src/native/auto-layout/AutoLayoutViewNativeComponent.ts","../src/native/auto-layout/AutoLayoutView.tsx","../src/native/cell-container/CellContainer.tsx","../src/PureComponentWrapper.tsx","../src/viewability/ViewToken.ts","../src/FlashListProps.ts","../src/utils/AverageWindow.ts","../src/utils/ContentContainerUtils.ts","../src/GridLayoutProviderWithProps.ts","../src/errors/CustomError.ts","../src/errors/ExceptionList.ts","../src/errors/Warnings.ts","../src/viewability/ViewabilityHelper.ts","../src/viewability/ViewabilityManager.ts","../node_modules/recyclerlistview/dist/reactnative/platform/reactnative/itemanimators/defaultjsanimator/DefaultJSItemAnimator.d.ts","../src/native/config/PlatformHelper.ts","../src/FlashList.tsx","../src/AnimatedFlashList.ts","../src/MasonryFlashList.tsx","../src/benchmark/AutoScrollHelper.ts","../src/benchmark/roundToDecimalPlaces.ts","../src/benchmark/JSFPSMonitor.ts","../src/benchmark/useBlankAreaTracker.ts","../src/benchmark/useBenchmark.ts","../src/benchmark/useDataMultiplier.ts","../src/benchmark/useFlatListBenchmark.ts","../src/index.ts","../src/__tests__/AverageWindow.test.ts","../src/__tests__/ContentContainerUtils.test.ts","../node_modules/@quilted/react-testing/build/typescript/types.d.ts","../node_modules/@quilted/react-testing/build/typescript/matchers/index.d.ts","../node_modules/@quilted/react-testing/build/typescript/environment.d.ts","../node_modules/@quilted/react-testing/build/typescript/implementations/test-renderer.d.ts","../node_modules/@quilted/react-testing/build/typescript/index.d.ts","../src/__tests__/helpers/mountFlashList.tsx","../src/__tests__/FlashList.test.tsx","../src/__tests__/GridLayoutProviderWithProps.test.ts","../src/__tests__/helpers/mountMasonryFlashList.tsx","../src/__tests__/MasonryFlashList.test.ts","../src/native/config/PlatformHelper.web.ts","../src/__tests__/PlatformHelper.web.test.ts","../src/__tests__/ViewabilityHelper.test.ts","../src/__tests__/useBlankAreaTracker.test.tsx","../src/native/auto-layout/AutoLayoutViewNativeComponent.android.ts","../src/native/auto-layout/AutoLayoutViewNativeComponent.ios.ts","../src/native/cell-container/CellContainer.android.ts","../src/native/cell-container/CellContainer.ios.ts","../src/native/cell-container/CellContainer.web.tsx","../src/native/config/PlatformHelper.android.ts","../src/native/config/PlatformHelper.ios.ts","../node_modules/@babel/types/lib/index.d.ts","../node_modules/@types/babel__generator/index.d.ts","../node_modules/@babel/parser/typings/babel-parser.d.ts","../node_modules/@types/babel__template/index.d.ts","../node_modules/@types/babel__traverse/index.d.ts","../node_modules/@types/babel__core/index.d.ts","../node_modules/@types/node/assert.d.ts","../node_modules/@types/node/assert/strict.d.ts","../node_modules/@types/node/globals.d.ts","../node_modules/@types/node/async_hooks.d.ts","../node_modules/@types/node/buffer.d.ts","../node_modules/@types/node/child_process.d.ts","../node_modules/@types/node/cluster.d.ts","../node_modules/@types/node/console.d.ts","../node_modules/@types/node/constants.d.ts","../node_modules/@types/node/crypto.d.ts","../node_modules/@types/node/dgram.d.ts","../node_modules/@types/node/diagnostics_channel.d.ts","../node_modules/@types/node/dns.d.ts","../node_modules/@types/node/dns/promises.d.ts","../node_modules/@types/node/domain.d.ts","../node_modules/@types/node/events.d.ts","../node_modules/@types/node/fs.d.ts","../node_modules/@types/node/fs/promises.d.ts","../node_modules/@types/node/http.d.ts","../node_modules/@types/node/http2.d.ts","../node_modules/@types/node/https.d.ts","../node_modules/@types/node/inspector.d.ts","../node_modules/@types/node/module.d.ts","../node_modules/@types/node/net.d.ts","../node_modules/@types/node/os.d.ts","../node_modules/@types/node/path.d.ts","../node_modules/@types/node/perf_hooks.d.ts","../node_modules/@types/node/process.d.ts","../node_modules/@types/node/punycode.d.ts","../node_modules/@types/node/querystring.d.ts","../node_modules/@types/node/readline.d.ts","../node_modules/@types/node/repl.d.ts","../node_modules/@types/node/stream.d.ts","../node_modules/@types/node/stream/promises.d.ts","../node_modules/@types/node/stream/consumers.d.ts","../node_modules/@types/node/stream/web.d.ts","../node_modules/@types/node/string_decoder.d.ts","../node_modules/@types/node/timers.d.ts","../node_modules/@types/node/timers/promises.d.ts","../node_modules/@types/node/tls.d.ts","../node_modules/@types/node/trace_events.d.ts","../node_modules/@types/node/tty.d.ts","../node_modules/@types/node/url.d.ts","../node_modules/@types/node/util.d.ts","../node_modules/@types/node/v8.d.ts","../node_modules/@types/node/vm.d.ts","../node_modules/@types/node/wasi.d.ts","../node_modules/@types/node/worker_threads.d.ts","../node_modules/@types/node/zlib.d.ts","../node_modules/@types/node/globals.global.d.ts","../node_modules/@types/node/index.d.ts","../node_modules/@types/graceful-fs/index.d.ts","../node_modules/@types/istanbul-lib-coverage/index.d.ts","../node_modules/@types/istanbul-lib-report/index.d.ts","../node_modules/@types/istanbul-reports/index.d.ts","../node_modules/chalk/index.d.ts","../node_modules/@sinclair/typebox/typebox.d.ts","../node_modules/@jest/schemas/build/index.d.ts","../node_modules/pretty-format/build/index.d.ts","../node_modules/jest-diff/build/index.d.ts","../node_modules/jest-matcher-utils/build/index.d.ts","../node_modules/@types/jest/index.d.ts","../node_modules/@types/json-schema/index.d.ts","../node_modules/@types/json5/index.d.ts","../node_modules/@types/parse-json/index.d.ts","../node_modules/@types/prettier/index.d.ts","../node_modules/@types/react-test-renderer/index.d.ts","../node_modules/@types/scheduler/index.d.ts","../node_modules/@types/stack-utils/index.d.ts","../node_modules/@types/websocket/index.d.ts","../node_modules/@types/yargs-parser/index.d.ts","../node_modules/@types/yargs/index.d.ts"],"fileInfos":[{"version":"f5c28122bee592cfaf5c72ed7bcc47f453b79778ffa6e301f45d21a0970719d4","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","e6b724280c694a9f588847f754198fb96c43d805f065c3a5b28bbc9594541c84","1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9",{"version":"3f149f903dd20dfeb7c80e228b659f0e436532de772469980dbd00702cc05cc1","affectsGlobalScope":true},{"version":"1272277fe7daa738e555eb6cc45ded42cc2d0f76c07294142283145d49e96186","affectsGlobalScope":true},{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"43fb1d932e4966a39a41b464a12a81899d9ae5f2c829063f5571b6b87e6d2f9c","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"0d5f52b3174bee6edb81260ebcd792692c32c81fd55499d69531496f3f2b25e7","affectsGlobalScope":true},{"version":"810627a82ac06fb5166da5ada4159c4ec11978dfbb0805fe804c86406dab8357","affectsGlobalScope":true},{"version":"181f1784c6c10b751631b24ce60c7f78b20665db4550b335be179217bacc0d5f","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"75ec0bdd727d887f1b79ed6619412ea72ba3c81d92d0787ccb64bab18d261f14","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"12a310447c5d23c7d0d5ca2af606e3bd08afda69100166730ab92c62999ebb9d","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596","affectsGlobalScope":true},{"version":"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"cd483c056da900716879771893a3c9772b66c3c88f8943b4205aec738a94b1d0","affectsGlobalScope":true},{"version":"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58","affectsGlobalScope":true},{"version":"c37f8a49593a0030eecb51bbfa270e709bec9d79a6cc3bb851ef348d4e6b26f8","affectsGlobalScope":true},"14a84fbe4ec531dcbaf5d2594fd95df107258e60ae6c6a076404f13c3f66f28e",{"version":"1c0e04c54479b57b49fec4e93556974b3d071b65d0b750897e07b3b7d2145fc5","affectsGlobalScope":true},"bc1852215dc1488e6747ca43ae0605041de22ab9a6eeef39542d29837919c414","ae6da60c852e7bacc4a49ff14a42dc1a3fdbb44e11bd9b4acb1bf3d58866ee71",{"version":"0dab023e564abb43c817779fff766e125017e606db344f9633fdba330c970532","affectsGlobalScope":true},"4cbd76eafece5844dc0a32807e68047aecbdd8d863edba651f34c050624f18df",{"version":"ecf78e637f710f340ec08d5d92b3f31b134a46a4fcf2e758690d8c46ce62cba6","affectsGlobalScope":true},"ea0aa24a32c073b8639aa1f3130ba0add0f0f2f76b314d9ba988a5cb91d7e3c4","f7b46d22a307739c145e5fddf537818038fdfffd580d79ed717f4d4d37249380","f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",{"version":"29b8a3a533884705024eab54e56465614ad167f5dd87fdc2567d8e451f747224","affectsGlobalScope":true},"4f2490e3f420ea6345cade9aee5eada76888848e053726956aaf2af8705477ea","b3ac03d0c853c0ac076a10cfef4dc21d810f54dac5899ade2b1c628c35263533","d17a689ac1bd689f37d6f0d3d9a21afac349e60633844044f7a7b7b9d6f7fd83","019650941b03d4978f62d21ae874788a665c02b54e3268ef2029b02d3b4f7561","ae591c8a4d5c7f7fa44b6965016391457d9c1fd763475f68340599a2a2987a24","fbdef0c642b82cc1713b965f07b4da8005bbbb2c026039bfdc15ca2d20769e38","c2c004e7f1a150541d06bc4a408b96e45ac1f08e0b1b35dfd07fc0f678205f95","1f2081eb2cbeb0828f9baa1dd12cf6d207f8104ae0b085ab9975d11adc7f7e6f","cda9069fc4c312ff484c1373455e4297a02d38ae3bd7d0959aad772a2809623c","c028d20108bcaa3b1fdf3514956a8a90ccf680f18672fa3c92ce5acf81d7ab23","1054f6e8774a75aaf17e7cfea4899344f69590b2db1e06da21048ed1e063c693","9533301b8f75664e1b40a8484a4fd9c77efc04aef526409c2447aab7d12ddc63","b78b5b3fdb4e30976c4263c66c0ad38fb81edcc8075a4160a39d99c6dedd35be","032b51d656feaece529823992f5a39fe9e24d44dfa21b3a149982f7787fc7bdf","5bbfdfb694b019cb2a2022fba361a7a857efc1fc2b77a892c92ebc1349b7e984","46bc25e3501d321a70d0878e82a1d47b16ab77bdf017c8fecc76343f50806a0d","42bacb33cddecbcfe3e043ee1117ba848801749e44f947626765b3e0aec74b1c","49dba0d7a37268e6ae2026e84ad4362eac7e776d816756abf649be7fa177dcd5","5f2b5ab209daae571eb9acc1fd2067ccc94e2a13644579a245875bc4f02b562f","f072acf9547f89b814b9fdb3e72f4ebb1649191591cec99db43d35383906f87f","42450dba65ba1307f27c914a8e45e0b602c6f8f78773c052e42b0b87562f081e","f5870d0ca7b0dfb7e2b9ba9abad3a2e2bffe5c711b53dab2e6e76ca2df58302b","aeb20169389e9f508b1a4eb2a30371b64d64bb7c8543120bc39a3c6b78adfcc9","2a3d3acbab8567057a943f9f56113c0144f5fc561623749fbd6bb5c2b33bf738","9cf21fdcd1beb5142a514887133fa59057e06275bb3070713f3b6d51e830ffa0","0ad4f0b67db47064b404df89c50f99552ce12d6c4bb6154255be61eb6beed094","f8a464b9999126fe1095968c266c0d9c6174612cf256379a1ed1993a87bccdc6","49f981ca657ac160b5de5919ee5602d48bc8f8aac0805107c2ce4fd41dc9a2a1","56e4e08d95a3a7886266a2b4f66b67065c340480d9f1beb73ed7578aa83c639a","eb4360d3818dcd879ee965ae2f4b3fdfdc4149db921b6be338cb7dc7c2bd6710","1c1275f325f13af001aa5873418cb497a26b4b8271f9ad20a45e33f61ea3f9d9","b33e8426136c4f9b349b02c940d23310d350179f790899733aa097ed76457061","05aab001669a230a88820be09a54031c45d9af2488b27d53d4a9c8880ce73e8f","d93a066d4b8b33335dfff910fb25abb8979f8814f8ba45ea902a1360907da1f6","41e97e42d182b4d5f0733ebaad69294faaa507d95e595f317168b8f2325da9ca","debc734fc99b6e1684ed565946bad008913c769d4d2e400d8722c0c23d079c06","5a9f7e087aacb01fa0cdbc36b703a60367239f62beed2507a507199e4c417549","c7c23798fbf564983ed69c1ced3371970d986aaed4801a6e0fb41862550dc034","921f5bce372610ae8948ade7d82decbd2cf56d263de578976189585edd0abac0","ac11f8b13beef593e2f097450a7e214b23dca0d428babd570a2f39582f10e9ab","2499beb5d3e2b4c606977bcc2e08b6ef77b2ecda70e78e0622f5af3bed95c9ba","a11057410396907b84051cbdb8b0cd7f7049d72b58d2b6ac1c14ac2608191a52","bb630c26d487cc45ed107f4f2d3c2a95434716f6367f059de734c40d288c31eb","67cbce0ccdfa96b25de478a93cc493266c152e256c3c96b3d16d1f811e3d881f","19905c928bc4c016d05d915625bb08568447266c4661232faf89f7ddc4417ccc","26204eb4c326e8c975f1b789cbf345c6820205bded6d72e57246a83918d3bc84","618f25b2d41a99216e71817a3bc578991eee86c858c3f0f62a9e70707f4d279d","4cd2947878536ec078e4115b7d53cdcd4dcecd3a8288760caa79098db4f8f61f","2129e984399e94c82b77a32b975f3371ca5ee96341ab9f123474f1a5a1a9921f","798120aaa4952d68cd4b43d6625524c62a135c2f5a3eb705caee98de2355230d","6047365397173788c34bd71fea2bf07a9036d981212efd059b33e52d2c405e97","d7e25d7c03ccf8b10972c2a3a57e29a8d9024e6dbc4ac223baf633a6e8c7145c","6c2e2dead2d80007ee44c429b925d0a7b86f8f3d4c237b2197f7db9f39545dc6","38fbc8f9610fbf4bf619854b26e28c4fbbab16dc1944c4317a4af9bf1ac08d8e","1bd0470a72e6869c330e6e978f15ef32ba2c245249aca097b410448152e8a06b","dd05d7970a92b789f7df3b2252574b2e60f1b9a3758e2839e167b498b8f77159","7092be1889127b2f319efd5d9bdcc0b5cf6fe0740e47247ed039446045518898","0a3d5dbf7c2091017e697ebf9af0a727571f5d99cb4c19e6856212a745c6c355","d05f9c767924db6fb89f6075acb64c042cebdb12779bbd1aaca12c850b772d49","d032678e20ff0f4b8ef6f1e4823b6ae37931b776e8381676dc9141999909b3d7","3e4ab0e8e96e968ac84a2484104892c881ded1757acd81b5e969b6229851f54c","d43a36641f5812794a3b4a941e3dfb5fa070f9fff64cfd6daf5291cb962c8b05","32468df81188116040636844517fbe4f67fc37af4fe565c7592353df8e11d2f3","c12b5f9bf412c891cad443ef00a378ad2d3f1301f140943414308665a7d90af8","cf1b65c20036885ed99ce1c18aa0a0ed66f42acd6d415e99b48a8fa4105c23ed","173aec8be1be982c8244df6f94880d77a9b766c8c1ec3eb0af662c8dc6da7f2e","08188020373062e07955835a996fda1aff97a89e57d469edc6b9210bd9c8926f","cad5c2c0085a3e3b74f58aa199944b25ed8d24f93f51c99ebe2463e4f1694785","3e2d93a797c41ab081fbcd80e959b7c30d5d1c358f091c22a6ebe416ef7c5e19","c440df5735a3305e7db118bf821efb597c8318910861f735372846db9f7b506b","d6d8de719a75e5d2ed9dd9d6a99296d1337259e1c96166579db50797edd72ede","32b4c732e183bf5d123f88d526ac21b71a681089c18d2d761be342df31179d94","212d16020e7dce1b5509f3b9813de73612de57c6a3d74536714eb88787b96dc3","1a63d5212341783aa49cf78d667bf2a6cd03208ea09620b2fc3e647ae07f4e0d","84ea58841272970e6e3247cba4dbb326cf22764c2f4bbcb03f1c634315bbbcb5","86f9fbecdd848d02c90f861cc9839d8f3449c518a77e77ea65362f6a4126c63b","ecdaf317a4a1e7e3540e2f1b6aae38acd78dd99d564b52f98eea7358ac74416d","c30430960f1a0552b3cdaf1ef8164fdd4f289c782a8912df5180d57bc9ddfc03","a348081c01502c9f87d39d9e4b5dd58e1111b34c62686d6e569c595a0417bb35","eff69aee13c76502a16b756cde9c451fb4b5c4234052f3b3bee9dbfe92e1b1d5","9943f44400939f4ff008a882ff71162f70ba0c2f735c9743fd4645ef5c925fc4","b7836eba6c5173a1683aee8aa1771ff339e795cb9c21411590edb910274febe4","6fe447aa7e6fabc4f6c536f2997e3b1116b7f73dbe5bf3fc8d958bad434e4a84","15d3908d453d14be4dae760122ed5d74ad789a19f1fec2edd4034e57217436e9","ef00bc701f382da70870ab7721ed8f6552a38e332e60370b93cf340b6470845c","18891a02fa046e57b43a543dddc7212086fcb04ae6c8e8f28f8605dd3ccf57ed",{"version":"5980a888624dce1b0937a0d21c623f97056501bb61a8da29cbe07f1a0be2c9a8","affectsGlobalScope":true},"590a41ccab332c66a6aa62746612b03ceb2e92cc1da58c140e90fb7ff6e8c851","dc1d2996f23fe7f0da0b2c843e05c0ac170a36b51da11e58de089d344de93c3b","78ff01b50e7e9761f239527ec70b96171bccc28a08d909243e193db03b6f6983","ed18472ee2247563a26d754dd4c8bd66383013df13ce7c2927b03cab1a27b7e8","28ac9ac1fa163e5f2321fafa49b9931908c0076216ed3c82646d79abdf79775e","07dd4bed8ddab685f82a2125bf3aa41b42e36f28c16a5aec7357b727649076fb","fc15a2216f29b825747c0c3a54d6989518dd0f4aa0b580520e5526b4a47bec8f","c656d5baf3d4a8f358fc083db04b0fda8cb8503a613a9ba42327ecbd7909773c","397c2c81eaeae1388f7459699d7606feecfc304b212eb9113407c1315746a578","c2d923e9adc26a3efe5186f3a4a72413d24c80f03b306c68c30fa146690fb101","d34782833b7d5f72486a5fb926d3d96198706ed76aeaf1d435c748ebcf9169fc","b093e56054755189dd891ea832dec40d729d110a0a3f432fff5ea5ab1078cdde","98affe620e6230a3888b445c32376e4edbf6b1b376a71f2bf9c07bee11fcdd65","1e05491bef32ff48393d605d557152735899da3d9b111ba3588a1800f2927f4a","1ff7813974b1b9a0524c1e5a99aa52a05e79fc7df7749ada75ded8c53fe1b7e0","cd8c517f54d4ff3475755b290d741c8799df3265ce73d454d8fafe423f8ff749","bf431147b104ae92d61de6b43e9f25d27e8d3eaeaffd612e0c0d3bb8e2423926","f0f21604ae8f880c0ab529f00303806fdeadc943e32a25ca063fc8fea0fa063c","8dc4f45212fba9381e1674e8bd934a588730efbb8a6681b661cad8cd09b081c5",{"version":"52bf774bd30177ebb3e450c808d8d46f67896848a942e6203ae78b65b33d0106","signature":"688c437017a53e69ff66aac2036a0d7f6263082f676a408c9998cbd87ea2ec73"},{"version":"8b6ee36fd764378c62dca37041c5a12fd5a77b9e853c78908b7ed1c90dc149e4","signature":"03846acca031c757d910dbc017d846c87574faf90bde82316fb9b8537896d5ee"},{"version":"0d089d33f31b56697d142aa7395738c0323cf761b4c79fd6bf65a54ab1ddf02f","signature":"027c87e1cb049497d4f185bc9b922ce91cad59832da8faf3411e6b298b9deb78"},{"version":"ec0982b9e7d6c1b6c80e2829c5909eefb9ecee687e60621e0bb937e8ad5d1d43","signature":"8478b617a5be940f1b4b4d19d2fc6149c21ac69c4a7e00c8a7db2c2c21aa2274"},{"version":"84c5fc9d0d22f4566791b88d5fc2c24f56508b50c9ce894ac549ebaa158b1fca","signature":"677ea66c6fa02f1cebf82df19f416a8302c7a7d10e2de265b162760fcd865eef"},{"version":"8455135ea42310a73404fa2513e212d170af1191584061f583ec1e0f6b75dd91","signature":"83e4298f0b6834e955ee6a76569d3e5b3192065d47f1daf4535bb9edb16e88cb"},{"version":"73529962207605bdc5285d5e745919b8d57b776daa0f22a14b75cd8a92d63af9","signature":"422fcd2a7fd87f05efdfaa6eab382ca607d5d54e1f175ba2efccd4aacd5433ef"},{"version":"ebe927d8a9739c9d32ef4df28c1c36cf82daa9abba7cdf3f79e320c5e99e99d8","signature":"2421f9c6b1ecedd50818719090a77e9d2748c2339c33f3d4817beebf7a39d211"},{"version":"165c56632fea46c85e2a62f1b4eae600b846ea0deacd3c137fde9bacb845c30e","signature":"79bf9e3846b43e706d181c00f3c1c50ae8fc60e587c97a16e521adc150317624"},{"version":"866e1d2cf16a41851b056a2cc0cdc5f0f00df0435376cc2c723a8c609f61fbd0","signature":"5f5bbca60f0bfed6ff714163c4e962a5e260e59db754c89ee2063403accd03e3"},{"version":"ecfa1b63e3829b310ac968b2cc1cc7016ba76ffb8532439aebecbcbc57173b99","signature":"2f1dda63ade2bd085704674523b56ede942bc8c2c37fe8ed9b9b0fdfd69b1262"},{"version":"51d2f746d7e599a5549f5a946565934b4556bb9155be1eed2c474e25f1474872","signature":"c15585fe8935ed5cfedec39b7d41ec49990973f40faaba4b3e14278861643d79"},{"version":"b1d1378906c54a2f4d230ad69d212beedd2552afe3f7ad171b7eacb4cecc26d7","signature":"f9e60e8f79a7f606f19e02e2d39a24995719767dbe587f564f970bb24e3ca29d"},{"version":"f5a156e5b3783ea0399ac0326b7ab31a00e8874c5fa9b5e26fac217da8b5adfd","signature":"cfa7179e0306fc04d93f062c96e7ae8bad58d0cc4a7aa0dd4494ff9d262b101c"},{"version":"3c9fefca9303bcfd5712de11a3cbda20b3d6e85f29019bc75cab24690fb0f90d","signature":"306683152ff5a6038cf05b03ddff85a15b1bc8e18ef268aad26b02fd8e0e8b9d"},"a11c3e55d22d6379fe0949793e2638a6b43aa6e9def4f7452c3e352a296ef8da",{"version":"2770956c9437d7d66650084891c559ff6bb94200b7e2820940fd5d5dd0efa489","signature":"2faaf4f254008bf5be0e145be10dba35dccfac7116e9083f9d697a476a8e7076"},{"version":"ceee917fd557b841b93f7e13103dfdad79d38fe9962408f538f27db03dc9368d","signature":"15003ff6ed10d259dca775c7e5f7a64b272a9c370b6085db2d42a2d4a1d81579"},{"version":"a1691ae6d70af82f3e26d9e2e021dc5063021bd9c335bfdb40dc97d3574d1b3f","signature":"cd1c566b611a70ff987a79d0465da67649a8ed7e7668feddfcdf6dceb01c09a8"},{"version":"a105417dd540f1a400f0665c877e5d7e48e2efe08f01c2e5c7272256e644faa5","signature":"b3a6ee392811d6cddb38378ebaa373d4a39aa7dc4ecac73497c6b33627e6430b"},{"version":"581b44cf6122e3ad267d6bda2428c214fef3d38b6d7249df9fa6bc240a880a78","signature":"0ca09d92d6469d906a3d1c7192a6294c7f65b75f4f7eb8072bbd1b68c7f021e1"},{"version":"2e6426c1a1ff8561aa5f01d9398426bf06e55307f688464939de3196f0d4c143","signature":"5357bd09c9816a9765e617f86a9b49f85133d0bc0f9c5e29e834f2f8e6d52acb"},{"version":"508279c48de5627ae6c30a0aee01f4391bf32450335d7f09d5dd82acbc4d13c5","signature":"11d546a505f70f9c5f8092916027d8045c280a817b709fcaf2c4e63fa026c89c"},{"version":"557f2e0a4e5ac8a59b7c3068b2b30162fb963d72d50152482ab8c414e81caf37","signature":"008eaae28119118f1c589a1e29ea7fd17277f2280d2d3bfddeacd71fd1671bb5"},{"version":"f45c172ca54fb28d64b2dd04e495f17038034e20f37bd286a9f3eeb286cf1388","signature":"75a8761564c8fc5581b062dd339ea698921baf60e52eae055c8177dfa89eba90"},{"version":"ea696a0517ad69afea472e47eb1f904aba1667f54d4557eb98b8c766469d56a2","signature":"7e125d9abc19f62d1480f6c04a45d7bb2c89153316245ae8b8e5a0234b078c4e"},{"version":"902937c505f88d8b5b32829b4c14243eb740013fd0e2f58e6485324bbfe197a6","signature":"dc7de7650e5a64fc010387db18e84d48fe8f562dbd9caac01e54f83681ac976b"},{"version":"842accda78bb1b6f494f264aae307b84d933486d607e91f6e1d3a4d2e4851783","signature":"430d9683c8e5aaab71f0e3b271c4240cd5120a91191f953722985499af51d7e6"},{"version":"45b1a895868587c78a2ddff937967669b4e1968ea72c01e1c2b6dd5993f53b36","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"99cab9373415bac71e9d2c84279782c0a361b59551d0ca8dfaee8d4c08ed3247","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"ba1fed463e8a21ffddb67a53df3f0d818b351991723736e167d065e2de1c7183",{"version":"22e311fec88bcc49b2b1fb3c9a7c082cd84b3388c9bcc7b9ef08253f6fa74e26","affectsGlobalScope":true},"c186097fd9b86681981cdeba08c0b6bbfcd8b562ab490c25656d85fef8f10c79","0b0c483e991e81c3f26e5f2da53ff26a15994c98c8b89cda1e4156dfc2428111","3340eb7b30bdee5f0349107d4068fd6f2f4712e11a2ba68e203b2f2489350317",{"version":"2000d60bd5195730ffff0d4ce9389003917928502c455ed2a9e296d3bf1a4b42","signature":"56335d3c9b867cc8654c05e633c508dd8de0038157f9958eb8794b7c123bb90e"},{"version":"dfceb5b9355a4a9002a7c291b1c3315511977c73cb23d9c123a72567783a18c0","signature":"b1802850887a3ea11a06df1fc1c65c6579332eefba1e63b3967a73dc937a2574"},{"version":"384fc0e3fa5966f524c96f1782b9d7a005346ba1621c43d0d1d819bf39077fbc","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"7fde517b3f03bb21ec3a46ba5f85c6797f8abf27deacb862183126e2f072788e","signature":"8b310edcfec83da25bc4f3adb20a7583bc5dae56d7d06c5b1431b76d390c1b72"},{"version":"894d93831d2afcd26f7362347e4960dd6d53f4153dad08813f3670e1327e387c","signature":"b1802850887a3ea11a06df1fc1c65c6579332eefba1e63b3967a73dc937a2574"},{"version":"8f9eac2c3ae305c25d4ffeff800b9811c8d3ec6a11b142fe96d08a2bc40f6440","signature":"08d6a2d1b004bbcac4249cd5baf6e9c662adc6139939c266b42e0422ef0c68b3"},{"version":"ac8980bdd810c30c444b59cca584c9b61d5ab274fa9474d778970537f3090240","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"1c024431c672cf9c6dcdb4d30c5b625435d81a5423b9d45e8de0082e969af8a8","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"eee1b57475023853cd09dd79b8d0d6639b6b82c3baee5863c2f2022b710f4102","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"377ba49d29102653a4b0c72b3870f9c599575df7db3a3fae7a21be5327ff84e2","signature":"c47f5db4df0a5031ed84bc6ee192c412b9e2d4d5e94681af77ccdcc25c851839"},{"version":"377ba49d29102653a4b0c72b3870f9c599575df7db3a3fae7a21be5327ff84e2","signature":"c47f5db4df0a5031ed84bc6ee192c412b9e2d4d5e94681af77ccdcc25c851839"},{"version":"39833acf7547216b2f31b2279dcfec3ed1359dec8adc9d1cb87c695ebf9bff94","signature":"7292d4dc9dac6d815dc30245a4a4a4959845d3a2b84ba0166857e4b23f2d033f"},{"version":"39833acf7547216b2f31b2279dcfec3ed1359dec8adc9d1cb87c695ebf9bff94","signature":"7292d4dc9dac6d815dc30245a4a4a4959845d3a2b84ba0166857e4b23f2d033f"},{"version":"529dd364d169ab3dbbb177ccdc4987c4a6f69187f553f3d36460ab65879ad998","signature":"3919e9d5911da2254732c31942e2cdc0057056ebfc2a16d34041c76a9b58d447"},{"version":"ebea587ca6477b9db29baf75d359924c55ab490fecdc38d7c0f16e589f0d27f9","signature":"0688c25f38e78e052338305d23046c7841074b3da5709a8f9e598ed705b9932b"},{"version":"de411013305dbe5c7a1ac13d2ea16dc36e52e6efd255b4e912fe53862058c649","signature":"2faaf4f254008bf5be0e145be10dba35dccfac7116e9083f9d697a476a8e7076"},"e432b56911b58550616fc4d54c1606f65fe98c74875b81d74601f5f965767c60","cc957354aa3c94c9961ebf46282cfde1e81d107fc5785a61f62c67f1dd3ac2eb","a46a2e69d12afe63876ec1e58d70e5dbee6d3e74132f4468f570c3d69f809f1c","93de1c6dab503f053efe8d304cb522bb3a89feab8c98f307a674a4fae04773e9","3b043cf9a81854a72963fdb57d1884fc4da1cf5be69b5e0a4c5b751e58cb6d88","dd5647a9ccccb2b074dca8a02b00948ac293091ebe73fdf2e6e98f718819f669","0cba3a5d7b81356222594442753cf90dd2892e5ccfe1d262aaca6896ba6c1380","a69c09dbea52352f479d3e7ac949fde3d17b195abe90b045d619f747b38d6d1a",{"version":"c2ab70bbc7a24c42a790890739dd8a0ba9d2e15038b40dff8163a97a5d148c00","affectsGlobalScope":true},"422dbb183fdced59425ca072c8bd09efaa77ce4e2ab928ec0d8a1ce062d2a45a",{"version":"712ba0d43b44d144dfd01593f61af6e2e21cfae83e834d297643e7973e55ed61","affectsGlobalScope":true},"1dab5ab6bcf11de47ab9db295df8c4f1d92ffa750e8f095e88c71ce4c3299628","f71f46ccd5a90566f0a37b25b23bc4684381ab2180bdf6733f4e6624474e1894",{"version":"54e65985a3ee3cec182e6a555e20974ea936fc8b8d1738c14e8ed8a42bd921d4","affectsGlobalScope":true},"82408ed3e959ddc60d3e9904481b5a8dc16469928257af22a3f7d1a3bc7fd8c4","98a3ebfa494b46265634a73459050befba5da8fdc6ca0ef9b7269421780f4ff3","34e5de87d983bc6aefef8b17658556e3157003e8d9555d3cb098c6bef0b5fbc8","cc0b61316c4f37393f1f9595e93b673f4184e9d07f4c127165a490ec4a928668","f27371653aded82b2b160f7a7033fb4a5b1534b6f6081ef7be1468f0f15327d3","c762cd6754b13a461c54b59d0ae0ab7aeef3c292c6cf889873f786ee4d8e75c9","f4ea7d5df644785bd9fbf419930cbaec118f0d8b4160037d2339b8e23c059e79",{"version":"bfea28e6162ed21a0aeed181b623dcf250aa79abf49e24a6b7e012655af36d81","affectsGlobalScope":true},"7a5459efa09ea82088234e6533a203d528c594b01787fb90fba148885a36e8b6","ae97e20f2e10dbeec193d6a2f9cd9a367a1e293e7d6b33b68bacea166afd7792","10d4796a130577d57003a77b95d8723530bbec84718e364aa2129fa8ffba0378","ad41bb744149e92adb06eb953da195115620a3f2ad48e7d3ae04d10762dae197","bf73c576885408d4a176f44a9035d798827cc5020d58284cb18d7573430d9022","7ae078ca42a670445ae0c6a97c029cb83d143d62abd1730efb33f68f0b2c0e82",{"version":"e8b18c6385ff784228a6f369694fcf1a6b475355ba89090a88de13587a9391d5","affectsGlobalScope":true},"5d0a9ea09d990b5788f867f1c79d4878f86f7384cb7dab38eecbf22f9efd063d","12eea70b5e11e924bb0543aea5eadc16ced318aa26001b453b0d561c2fd0bd1e","08777cd9318d294646b121838574e1dd7acbb22c21a03df84e1f2c87b1ad47f2","08a90bcdc717df3d50a2ce178d966a8c353fd23e5c392fd3594a6e39d9bb6304",{"version":"4cd4cff679c9b3d9239fd7bf70293ca4594583767526916af8e5d5a47d0219c7","affectsGlobalScope":true},"2a12d2da5ac4c4979401a3f6eaafa874747a37c365e4bc18aa2b171ae134d21b","002b837927b53f3714308ecd96f72ee8a053b8aeb28213d8ec6de23ed1608b66","1dc9c847473bb47279e398b22c740c83ea37a5c88bf66629666e3cf4c5b9f99c","a9e4a5a24bf2c44de4c98274975a1a705a0abbaad04df3557c2d3cd8b1727949","00fa7ce8bc8acc560dc341bbfdf37840a8c59e6a67c9bfa3fa5f36254df35db2","1b952304137851e45bc009785de89ada562d9376177c97e37702e39e60c2f1ff",{"version":"806ef4cac3b3d9fa4a48d849c8e084d7c72fcd7b16d76e06049a9ed742ff79c0","affectsGlobalScope":true},"44b8b584a338b190a59f4f6929d072431950c7bd92ec2694821c11bce180c8a5","5f0ed51db151c2cdc4fa3bb0f44ce6066912ad001b607a34e65a96c52eb76248",{"version":"3345c276cab0e76dda86c0fb79104ff915a4580ba0f3e440870e183b1baec476","affectsGlobalScope":true},"664d8f2d59164f2e08c543981453893bc7e003e4dfd29651ce09db13e9457980","e383ff72aabf294913f8c346f5da1445ae6ad525836d28efd52cbadc01a361a6","f52fbf64c7e480271a9096763c4882d356b05cab05bf56a64e68a95313cd2ce2","59bdb65f28d7ce52ccfc906e9aaf422f8b8534b2d21c32a27d7819be5ad81df7",{"version":"3a2da34079a2567161c1359316a32e712404b56566c45332ac9dcee015ecce9f","affectsGlobalScope":true},"28a2e7383fd898c386ffdcacedf0ec0845e5d1a86b5a43f25b86bc315f556b79","3aff9c8c36192e46a84afe7b926136d520487155154ab9ba982a8b544ea8fc95","a880cf8d85af2e4189c709b0fea613741649c0e40fffb4360ec70762563d5de0","85bbf436a15bbeda4db888be3062d47f99c66fd05d7c50f0f6473a9151b6a070","9f9c49c95ecd25e0cb2587751925976cf64fd184714cb11e213749c80cf0f927","f0c75c08a71f9212c93a719a25fb0320d53f2e50ca89a812640e08f8ad8c408c",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"9cafe917bf667f1027b2bb62e2de454ecd2119c80873ad76fc41d941089753b8","3ebae8c00411116a66fca65b08228ea0cf0b72724701f9b854442100aab55aba","8b06ac3faeacb8484d84ddb44571d8f410697f98d7bfa86c0fda60373a9f5215","7eb06594824ada538b1d8b48c3925a83e7db792f47a081a62cf3e5c4e23cf0ee","f5638f7c2f12a9a1a57b5c41b3c1ea7db3876c003bab68e6a57afd6bcc169af0","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","7980bf9d2972585cdf76b5a72105f7817be0723ccb2256090f6335f45b462abe","301d7466eb591139c7d456958f732153b3400f3243f68d3321956b43a64769e9","22f13de9e2fe5f0f4724797abd3d34a1cdd6e47ef81fc4933fea3b8bf4ad524b","e3ba509d3dce019b3190ceb2f3fc88e2610ab717122dabd91a9efaa37804040d","cda0cb09b995489b7f4c57f168cd31b83dcbaa7aad49612734fb3c9c73f6e4f2",{"version":"2abad7477cf6761b55c18bea4c21b5a5dcf319748c13696df3736b35f8ac149e","affectsGlobalScope":true},"d38e588a10943bbab1d4ce03d94759bf065ff802a9a72fc57aa75a72f1725b71","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","2b8264b2fefd7367e0f20e2c04eed5d3038831fe00f5efbc110ff0131aab899b","6209c901f30cc321f4b86800d11fad3d67e73a3308f19946b1bc642af0280298","60aaac5fb1858fbd4c4eb40e01706eb227eed9eca5c665564bd146971280dbd3","74b0245c42990ed8a849df955db3f4362c81b13f799ebc981b7bec2d5b414a57","b0d10e46cfe3f6c476b69af02eaa38e4ccc7430221ce3109ae84bb9fb8282298","4266ccd2cf1d6a281efd9c7ddf9efd7daecf76575364148bd233e18919cac3ed","70e9a18da08294f75bf23e46c7d69e67634c0765d355887b9b41f0d959e1426e","105b9a2234dcb06ae922f2cd8297201136d416503ff7d16c72bfc8791e9895c1"],"options":{"composite":true,"declaration":true,"declarationMap":true,"downlevelIteration":true,"esModuleInterop":true,"experimentalDecorators":true,"importHelpers":true,"jsx":2,"noEmitOnError":false,"noImplicitAny":true,"noUnusedLocals":true,"outDir":"./","rootDir":"../src","skipLibCheck":true,"sourceMap":true,"strictNullChecks":true,"target":1,"tsBuildInfoFile":"./tsconfig.tsbuildinfo"},"fileIdsList":[[211,260],[260],[260,273],[53,190,260],[192,194,260],[190,192,193,260],[53,260],[53,140,260],[69,260],[211,212,213,214,215,260],[211,213,260],[233,260,267],[260,269],[260,270],[260,275,277],[217,260],[220,260],[221,226,260],[222,232,233,240,249,259,260],[222,223,232,240,260],[224,260],[225,226,233,241,260],[226,249,256,260],[227,229,232,240,260],[228,260],[229,230,260],[231,232,260],[232,260],[232,233,234,249,259,260],[232,233,234,249,260],[235,240,249,259,260],[232,233,235,236,240,249,256,259,260],[235,237,249,256,259,260],[217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266],[232,238,260],[239,259,260],[229,232,240,249,260],[241,260],[242,260],[220,243,260],[244,258,260,264],[245,260],[246,260],[232,247,260],[247,248,260,262],[232,249,250,251,260],[249,251,260],[249,250,260],[252,260],[253,260],[232,254,255,260],[254,255,260],[226,240,249,256,260],[257,260],[240,258,260],[221,235,246,259,260],[226,260],[249,260,261],[260,262],[260,263],[221,226,232,234,243,249,259,260,262,264],[249,260,265],[76,77,260],[53,58,64,65,68,71,72,73,76,260],[74,260],[84,260],[53,57,82,260],[53,54,57,58,62,75,76,260],[53,76,105,106,260],[53,54,57,58,62,76,260],[82,91,260],[53,54,62,75,76,93,260],[53,55,58,61,62,65,75,76,260],[53,54,57,62,76,260],[53,54,57,62,260],[53,54,55,58,60,62,63,75,76,260],[53,76,260],[53,75,76,260],[53,54,57,58,61,62,75,76,82,93,260],[53,55,58,260],[53,54,57,60,75,76,93,103,260],[53,54,60,76,103,105,260],[53,54,57,60,62,93,103,260],[53,54,55,58,60,61,75,76,93,260],[58,260],[53,55,58,59,60,61,75,76,260],[82,260],[83,260],[53,54,55,57,58,61,66,67,75,76,260],[58,59,260],[53,64,65,70,75,76,260],[53,56,64,70,75,76,260],[53,58,62,260],[53,118,260],[53,57,260],[57,260],[76,260],[75,260],[66,74,76,260],[53,54,57,58,61,75,76,260],[128,260],[53,56,57,260],[91,260],[44,45,46,47,48,55,56,57,58,59,60,61,62,63,64,65,66,67,68,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,260],[140,260],[46,260],[49,50,51,52,260],[232,235,237,240,259,260,267],[260,287],[260,275],[260,272,276],[260,274],[151,260],[53,141,142,143,144,146,147,148,149,150,157,260],[53,140,147,150,151,260],[143,144,260],[142,143,144,147,260],[143,260],[155,260],[157,260],[144,260],[53,144,260],[141,142,143,144,145,146,147,149,150,151,152,153,154,156,260],[149,260],[158,260],[43,140,166,177,260],[43,53,140,157,159,162,163,164,166,168,169,170,171,172,174,176,260],[43,53,140,162,165,260],[43,157,166,167,168,260],[43,53,140,165,166,168,170,171,177,260],[43,53,260],[43,167,260],[43,168,260],[43,53,140,157,162,163,166,172,191,195,260],[43,140,157,177,195,260],[43,53,140,157,177,179,191,198,260],[43,200,260],[43,157,170,171,173,260],[43,53,140,166,177,191,194,260],[43,53,140,166,179,191,194,260],[43,53,177,183,194,195,260],[43,260],[43,181,260],[43,53,177,180,181,182,183,260],[43,53,157,162,177,260],[43,53,140,180,182,184,260],[43,162,163,165,166,177,178,179,180,182,183,184,185,186,260],[43,53,140,160,161,260],[43,140,160,260],[43,140,260],[43,53,140,260],[43,157,260],[43,157,175,260],[43,53,140,157,175,260],[43,140,157,166,260],[43,140,157,170,171,260],[43,140,165,173,177,260],[53,140,166],[53,157,166,169],[53,140,162,165],[157,166],[53,140,166,177],[53],[191],[53,166,177,191,194],[53,166,179,191,194],[53,177,182,183,187],[53,162,177],[140,184],[162,163,165,166,177,178,179,180,182,183,184,185,186],[53,140],[140,160],[140],[157],[53,157],[140,157,166],[140,157],[177]],"referencedMap":[[213,1],[211,2],[274,3],[192,4],[193,5],[194,6],[191,4],[190,7],[69,8],[70,9],[273,2],[216,10],[212,1],[214,11],[215,1],[268,12],[269,2],[270,13],[271,14],[278,15],[279,2],[280,2],[217,16],[218,16],[220,17],[221,18],[222,19],[223,20],[224,21],[225,22],[226,23],[227,24],[228,25],[229,26],[230,26],[231,27],[232,28],[233,29],[234,30],[219,2],[266,2],[235,31],[236,32],[237,33],[267,34],[238,35],[239,36],[240,37],[241,38],[242,39],[243,40],[244,41],[245,42],[246,43],[247,44],[248,45],[249,46],[251,47],[250,48],[252,49],[253,50],[254,51],[255,52],[256,53],[257,54],[258,55],[259,56],[260,57],[261,58],[262,59],[263,60],[264,61],[265,62],[281,2],[282,2],[51,2],[78,63],[79,2],[74,64],[80,2],[81,65],[85,66],[86,2],[87,67],[88,68],[107,69],[89,2],[90,70],[92,71],[94,72],[95,73],[96,74],[63,74],[97,75],[64,76],[98,77],[99,68],[100,78],[101,79],[102,2],[60,80],[104,81],[106,82],[105,83],[103,84],[65,75],[61,85],[62,86],[108,2],[91,87],[83,87],[84,88],[68,89],[66,2],[67,2],[109,87],[110,90],[111,2],[112,71],[71,91],[72,92],[113,2],[114,93],[115,2],[116,2],[117,2],[119,94],[120,2],[56,7],[121,7],[122,95],[123,96],[124,2],[125,97],[127,97],[126,97],[76,98],[75,99],[77,97],[73,100],[128,2],[129,101],[58,102],[130,66],[131,66],[132,103],[133,87],[118,2],[134,2],[135,2],[136,2],[137,7],[138,2],[82,2],[140,104],[44,2],[45,105],[46,106],[48,2],[47,2],[93,2],[54,2],[139,105],[55,2],[59,85],[57,7],[283,7],[49,2],[53,107],[284,2],[52,2],[285,2],[286,108],[287,2],[288,109],[272,2],[50,2],[276,110],[277,111],[275,112],[149,2],[154,113],[151,114],[158,115],[147,116],[148,117],[141,2],[142,2],[145,116],[144,118],[156,119],[155,120],[153,116],[143,121],[146,122],[157,123],[175,124],[152,2],[150,7],[159,125],[43,2],[8,2],[9,2],[11,2],[10,2],[2,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[3,2],[4,2],[23,2],[20,2],[21,2],[22,2],[24,2],[25,2],[26,2],[5,2],[27,2],[28,2],[29,2],[30,2],[6,2],[31,2],[32,2],[33,2],[34,2],[7,2],[35,2],[40,2],[41,2],[36,2],[37,2],[38,2],[39,2],[1,2],[42,2],[178,126],[177,127],[166,128],[169,129],[179,130],[164,131],[188,132],[189,133],[196,134],[197,135],[199,136],[201,137],[202,138],[195,139],[198,140],[203,141],[180,142],[182,143],[181,142],[184,144],[183,145],[185,142],[186,146],[170,142],[171,142],[172,142],[187,147],[162,148],[204,149],[205,149],[161,149],[160,131],[206,150],[207,150],[163,151],[208,131],[209,152],[210,152],[176,153],[200,154],[167,142],[168,155],[165,142],[173,156],[174,157]],"exportedModulesMap":[[213,1],[211,2],[274,3],[192,4],[193,5],[194,6],[191,4],[190,7],[69,8],[70,9],[273,2],[216,10],[212,1],[214,11],[215,1],[268,12],[269,2],[270,13],[271,14],[278,15],[279,2],[280,2],[217,16],[218,16],[220,17],[221,18],[222,19],[223,20],[224,21],[225,22],[226,23],[227,24],[228,25],[229,26],[230,26],[231,27],[232,28],[233,29],[234,30],[219,2],[266,2],[235,31],[236,32],[237,33],[267,34],[238,35],[239,36],[240,37],[241,38],[242,39],[243,40],[244,41],[245,42],[246,43],[247,44],[248,45],[249,46],[251,47],[250,48],[252,49],[253,50],[254,51],[255,52],[256,53],[257,54],[258,55],[259,56],[260,57],[261,58],[262,59],[263,60],[264,61],[265,62],[281,2],[282,2],[51,2],[78,63],[79,2],[74,64],[80,2],[81,65],[85,66],[86,2],[87,67],[88,68],[107,69],[89,2],[90,70],[92,71],[94,72],[95,73],[96,74],[63,74],[97,75],[64,76],[98,77],[99,68],[100,78],[101,79],[102,2],[60,80],[104,81],[106,82],[105,83],[103,84],[65,75],[61,85],[62,86],[108,2],[91,87],[83,87],[84,88],[68,89],[66,2],[67,2],[109,87],[110,90],[111,2],[112,71],[71,91],[72,92],[113,2],[114,93],[115,2],[116,2],[117,2],[119,94],[120,2],[56,7],[121,7],[122,95],[123,96],[124,2],[125,97],[127,97],[126,97],[76,98],[75,99],[77,97],[73,100],[128,2],[129,101],[58,102],[130,66],[131,66],[132,103],[133,87],[118,2],[134,2],[135,2],[136,2],[137,7],[138,2],[82,2],[140,104],[44,2],[45,105],[46,106],[48,2],[47,2],[93,2],[54,2],[139,105],[55,2],[59,85],[57,7],[283,7],[49,2],[53,107],[284,2],[52,2],[285,2],[286,108],[287,2],[288,109],[272,2],[50,2],[276,110],[277,111],[275,112],[149,2],[154,113],[151,114],[158,115],[147,116],[148,117],[141,2],[142,2],[145,116],[144,118],[156,119],[155,120],[153,116],[143,121],[146,122],[157,123],[175,124],[152,2],[150,7],[159,125],[43,2],[8,2],[9,2],[11,2],[10,2],[2,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[3,2],[4,2],[23,2],[20,2],[21,2],[22,2],[24,2],[25,2],[26,2],[5,2],[27,2],[28,2],[29,2],[30,2],[6,2],[31,2],[32,2],[33,2],[34,2],[7,2],[35,2],[40,2],[41,2],[36,2],[37,2],[38,2],[39,2],[1,2],[42,2],[178,158],[177,159],[166,160],[169,161],[179,162],[164,163],[196,164],[199,164],[195,165],[198,166],[184,167],[183,168],[186,169],[187,170],[162,171],[204,172],[205,172],[161,172],[160,163],[206,173],[207,173],[163,171],[208,163],[209,174],[210,174],[176,174],[200,175],[168,176],[173,177],[174,178]],"semanticDiagnosticsPerFile":[213,211,274,192,193,194,191,190,69,70,273,216,212,214,215,268,269,270,271,278,279,280,217,218,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,219,266,235,236,237,267,238,239,240,241,242,243,244,245,246,247,248,249,251,250,252,253,254,255,256,257,258,259,260,261,262,263,264,265,281,282,51,78,79,74,80,81,85,86,87,88,107,89,90,92,94,95,96,63,97,64,98,99,100,101,102,60,104,106,105,103,65,61,62,108,91,83,84,68,66,67,109,110,111,112,71,72,113,114,115,116,117,119,120,56,121,122,123,124,125,127,126,76,75,77,73,128,129,58,130,131,132,133,118,134,135,136,137,138,82,140,44,45,46,48,47,93,54,139,55,59,57,283,49,53,284,52,285,286,287,288,272,50,276,277,275,149,154,151,158,147,148,141,142,145,144,156,155,153,143,146,157,175,152,150,159,43,8,9,11,10,2,12,13,14,15,16,17,18,19,3,4,23,20,21,22,24,25,26,5,27,28,29,30,6,31,32,33,34,7,35,40,41,36,37,38,39,1,42,178,177,166,169,179,164,188,189,196,197,199,201,202,195,198,203,180,182,181,184,183,185,186,170,171,172,187,162,204,205,161,160,206,207,163,208,209,210,176,200,167,168,165,173,174]},"version":"4.7.4"}
\ No newline at end of file
diff --git a/node_modules/@shopify/flash-list/ios/Sources/AutoLayoutView.swift b/node_modules/@shopify/flash-list/ios/Sources/AutoLayoutView.swift
-index f18e92c..71b63dc 100644
+index f18e92c..f166553 100644
--- a/node_modules/@shopify/flash-list/ios/Sources/AutoLayoutView.swift
+++ b/node_modules/@shopify/flash-list/ios/Sources/AutoLayoutView.swift
@@ -4,31 +4,35 @@ import UIKit
@@ -973,7 +973,7 @@ index f18e92c..71b63dc 100644
} else {
assertionFailure("CellRendererComponent outer view should always be CellContainer. Learn more here: https://shopify.github.io/flash-list/docs/usage#cellrenderercomponent.")
return nil
-@@ -106,7 +128,7 @@ import UIKit
+@@ -106,12 +128,16 @@ import UIKit
/// Checks for overlaps or gaps between adjacent items and then applies a correction.
/// Performance: RecyclerListView renders very small number of views and this is not going to trigger multiple layouts on the iOS side.
@@ -982,7 +982,16 @@ index f18e92c..71b63dc 100644
var maxBound: CGFloat = 0
var minBound: CGFloat = CGFloat(Int.max)
var maxBoundNextCell: CGFloat = 0
-@@ -192,7 +214,7 @@ import UIKit
+ let correctedScrollOffset = scrollOffset - (horizontal ? frame.minX : frame.minY)
+ lastMaxBoundOverall = 0
++ if cellContainers.count == 1 {
++ let firstCellContainer = cellContainers[0]
++ lastMaxBoundOverall = horizontal ? firstCellContainer.frame.maxX : firstCellContainer.frame.maxY
++ }
+ cellContainers.indices.dropLast().forEach { index in
+ let cellContainer = cellContainers[index]
+ let cellTop = cellContainer.frame.minY
+@@ -192,7 +218,7 @@ import UIKit
lastMinBound = minBound
}
@@ -991,7 +1000,7 @@ index f18e92c..71b63dc 100644
lastMaxBoundOverall = max(lastMaxBoundOverall, horizontal ? currentCell.frame.maxX : currentCell.frame.maxY, horizontal ? nextCell.frame.maxX : nextCell.frame.maxY)
}
-@@ -217,7 +239,7 @@ import UIKit
+@@ -217,7 +243,7 @@ import UIKit
/// It's important to avoid correcting views outside the render window. An item that isn't being recycled might still remain in the view tree. If views outside get considered then gaps between unused items will cause algorithm to fail.
func isWithinBounds(
@@ -1000,21 +1009,18 @@ index f18e92c..71b63dc 100644
scrollOffset: CGFloat,
renderAheadOffset: CGFloat,
windowSize: CGFloat,
-@@ -260,10 +282,10 @@ import UIKit
+@@ -260,17 +286,18 @@ import UIKit
}
private func footerDiff() -> CGFloat {
- if subviews.count == 0 {
-+ if viewsToLayout.count == 0 {
- lastMaxBoundOverall = 0
+- lastMaxBoundOverall = 0
- } else if subviews.count == 1 {
- let firstChild = subviews[0]
-+ } else if viewsToLayout.count == 1 {
-+ let firstChild = viewsToLayout[0]
- lastMaxBoundOverall = horizontal ? firstChild.frame.maxX : firstChild.frame.maxY
- }
+- lastMaxBoundOverall = horizontal ? firstChild.frame.maxX : firstChild.frame.maxY
+- }
let autoLayoutEnd = horizontal ? frame.width : frame.height
-@@ -271,6 +293,13 @@ import UIKit
+ return lastMaxBoundOverall - autoLayoutEnd
}
private func footer() -> UIView? {
diff --git a/patches/react-native-modal+13.0.1.patch b/patches/react-native-modal+13.0.1.patch
index 5bfb2cc5f0b0..cc9c8531e3a3 100644
--- a/patches/react-native-modal+13.0.1.patch
+++ b/patches/react-native-modal+13.0.1.patch
@@ -11,7 +11,7 @@ index b63bcfc..bd6419e 100644
buildPanResponder: () => void;
getAccDistancePerDirection: (gestureState: PanResponderGestureState) => number;
diff --git a/node_modules/react-native-modal/dist/modal.js b/node_modules/react-native-modal/dist/modal.js
-index 80f4e75..3ba8b8c 100644
+index 80f4e75..5a58eae 100644
--- a/node_modules/react-native-modal/dist/modal.js
+++ b/node_modules/react-native-modal/dist/modal.js
@@ -75,6 +75,13 @@ export class ReactNativeModal extends React.Component {
@@ -48,7 +48,17 @@ index 80f4e75..3ba8b8c 100644
if (this.didUpdateDimensionsEmitter) {
this.didUpdateDimensionsEmitter.remove();
}
-@@ -525,7 +540,7 @@ export class ReactNativeModal extends React.Component {
+@@ -464,6 +479,9 @@ export class ReactNativeModal extends React.Component {
+ InteractionManager.clearInteractionHandle(this.interactionHandle);
+ this.interactionHandle = null;
+ }
++ if (this.state.isVisible) {
++ this.props.onModalHide();
++ }
+ }
+ componentDidUpdate(prevProps) {
+ // If the animations have been changed then rebuild them to make sure we're
+@@ -525,7 +543,7 @@ export class ReactNativeModal extends React.Component {
}
return (React.createElement(Modal, Object.assign({ transparent: true, animationType: 'none', visible: this.state.isVisible, onRequestClose: onBackButtonPress }, otherProps),
this.makeBackdrop(),
diff --git a/src/CONST.ts b/src/CONST.ts
index 704a3623a75d..601258890e33 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -73,6 +73,7 @@ const onboardingChoices = {
type OnboardingPurposeType = ValueOf;
const CONST = {
+ RECENT_WAYPOINTS_NUMBER: 20,
DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL],
// Note: Group and Self-DM excluded as these are not tied to a Workspace
@@ -208,27 +209,12 @@ const CONST = {
// Sizes needed for report empty state background image handling
EMPTY_STATE_BACKGROUND: {
ASPECT_RATIO: 3.72,
+ OVERLAP: 60,
SMALL_SCREEN: {
IMAGE_HEIGHT: 300,
- CONTAINER_MINHEIGHT: 200,
- VIEW_HEIGHT: 240,
},
WIDE_SCREEN: {
IMAGE_HEIGHT: 450,
- CONTAINER_MINHEIGHT: 500,
- VIEW_HEIGHT: 390,
- },
- MONEY_OR_TASK_REPORT: {
- SMALL_SCREEN: {
- IMAGE_HEIGHT: 300,
- CONTAINER_MINHEIGHT: 280,
- VIEW_HEIGHT: 240,
- },
- WIDE_SCREEN: {
- IMAGE_HEIGHT: 450,
- CONTAINER_MINHEIGHT: 280,
- VIEW_HEIGHT: 390,
- },
},
},
@@ -942,6 +928,7 @@ const CONST = {
RESIZE_DEBOUNCE_TIME: 100,
},
SEARCH_TABLE_COLUMNS: {
+ RECEIPT: 'receipt',
DATE: 'date',
MERCHANT: 'merchant',
FROM: 'from',
@@ -2069,7 +2056,7 @@ const CONST = {
LOGIN_CHARACTER_LIMIT: 254,
CATEGORY_NAME_LIMIT: 256,
TAG_NAME_LIMIT: 256,
- REPORT_NAME_LIMIT: 256,
+ REPORT_NAME_LIMIT: 100,
TITLE_CHARACTER_LIMIT: 100,
DESCRIPTION_LIMIT: 500,
WORKSPACE_NAME_CHARACTER_LIMIT: 80,
@@ -4765,6 +4752,7 @@ const CONST = {
MAX_TAX_RATE_DECIMAL_PLACES: 4,
DOWNLOADS_PATH: '/Downloads',
+ DOWNLOADS_TIMEOUT: 5000,
NEW_EXPENSIFY_PATH: '/New Expensify',
ENVIRONMENT_SUFFIX: {
@@ -4782,6 +4770,7 @@ const CONST = {
SEARCH_DATA_TYPES: {
TRANSACTION: 'transaction',
+ REPORT: 'report',
},
REFERRER: {
diff --git a/src/Expensify.tsx b/src/Expensify.tsx
index 6205afb9c03c..7a6203a44068 100644
--- a/src/Expensify.tsx
+++ b/src/Expensify.tsx
@@ -1,3 +1,4 @@
+import {Audio} from 'expo-av';
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {NativeEventSubscription} from 'react-native';
import {AppState, Linking} from 'react-native';
@@ -108,6 +109,9 @@ function Expensify({
const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]);
const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);
+ const isAuthenticatedRef = useRef(false);
+ isAuthenticatedRef.current = isAuthenticated;
+
const contextValue = useMemo(
() => ({
isSplashHidden,
@@ -194,7 +198,8 @@ function Expensify({
// Open chat report from a deep link (only mobile native)
Linking.addEventListener('url', (state) => {
- Report.openReportFromDeepLink(state.url);
+ // We need to pass 'isAuthenticated' to avoid loading a non-existing profile page twice
+ Report.openReportFromDeepLink(state.url, !isAuthenticatedRef.current);
});
return () => {
@@ -206,6 +211,11 @@ function Expensify({
// eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want this effect to run again
}, []);
+ // This is being done since we want to play sound even when iOS device is on silent mode, to align with other platforms.
+ useEffect(() => {
+ Audio.setAudioModeAsync({playsInSilentModeIOS: true});
+ }, []);
+
// Display a blank page until the onyx migration completes
if (!isOnyxMigrated) {
return null;
diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx
index 296ecce7d092..9ad4643e834a 100644
--- a/src/components/AddressForm.tsx
+++ b/src/components/AddressForm.tsx
@@ -4,6 +4,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import type {MaybePhraseKey} from '@libs/Localize';
+import Navigation from '@libs/Navigation/Navigation';
import * as ValidationUtils from '@libs/ValidationUtils';
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
@@ -148,6 +149,8 @@ function AddressForm({
label={translate('common.addressLine', {lineNumber: 1})}
onValueChange={(data: unknown, key: unknown) => {
onAddressChanged(data, key);
+ // This enforces the country selector to use the country from address instead of the country from URL
+ Navigation.setParams({country: undefined});
}}
defaultValue={street1}
renamedInputKeys={{
diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx
index 17a2f6212447..d1dc42bb4678 100644
--- a/src/components/AddressSearch/index.tsx
+++ b/src/components/AddressSearch/index.tsx
@@ -23,7 +23,25 @@ import CONST from '@src/CONST';
import type {Address} from '@src/types/onyx/PrivatePersonalDetails';
import CurrentLocationButton from './CurrentLocationButton';
import isCurrentTargetInsideContainer from './isCurrentTargetInsideContainer';
-import type {AddressSearchProps} from './types';
+import listViewOverflow from './listViewOverflow';
+import type {AddressSearchProps, PredefinedPlace} from './types';
+
+/**
+ * Check if the place matches the search by the place name or description.
+ * @param search The search string for a place
+ * @param place The place to check for a match on the search
+ * @returns true if search is related to place, otherwise it returns false.
+ */
+function isPlaceMatchForSearch(search: string, place: PredefinedPlace): boolean {
+ if (!search) {
+ return true;
+ }
+ if (!place) {
+ return false;
+ }
+ const fullSearchSentence = `${place.name ?? ''} ${place.description}`;
+ return search.split(' ').every((searchTerm) => !searchTerm || (searchTerm && fullSearchSentence.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())));
+}
// The error that's being thrown below will be ignored until we fork the
// react-native-google-places-autocomplete repo and replace the
@@ -41,6 +59,7 @@ function AddressSearch(
isLimitedToUSA = false,
label,
maxInputLength,
+ onFocus,
onBlur,
onInputChange,
onPress,
@@ -298,10 +317,16 @@ function AddressSearch(
};
}, []);
+ const filteredPredefinedPlaces = useMemo(() => {
+ if (!isOffline || !searchValue) {
+ return predefinedPlaces ?? [];
+ }
+ return predefinedPlaces?.filter((predefinedPlace) => isPlaceMatchForSearch(searchValue, predefinedPlace)) ?? [];
+ }, [isOffline, predefinedPlaces, searchValue]);
+
const listEmptyComponent = useCallback(
- () =>
- !!isOffline || !isTyping ? null : {translate('common.noResultsFound')},
- [isOffline, isTyping, styles, translate],
+ () => (!isTyping ? null : {translate('common.noResultsFound')}),
+ [isTyping, styles, translate],
);
const listLoader = useCallback(
@@ -338,11 +363,10 @@ function AddressSearch(
ref={containerRef}
>
- {!!title && {title}}
+ {!!title && {title}}
{subtitle}
);
@@ -385,6 +409,7 @@ function AddressSearch(
shouldSaveDraft,
onFocus: () => {
setIsFocused(true);
+ onFocus?.();
},
onBlur: (event) => {
if (!isCurrentTargetInsideContainer(event, containerRef)) {
@@ -414,10 +439,18 @@ function AddressSearch(
}}
styles={{
textInputContainer: [styles.flexColumn],
- listView: [StyleUtils.getGoogleListViewStyle(displayListViewBorder), styles.overflowAuto, styles.borderLeft, styles.borderRight, !isFocused && {height: 0}],
+ listView: [
+ StyleUtils.getGoogleListViewStyle(displayListViewBorder),
+ listViewOverflow,
+ styles.borderLeft,
+ styles.borderRight,
+ styles.flexGrow0,
+ !isFocused && styles.h0,
+ ],
row: [styles.pv4, styles.ph3, styles.overflowAuto],
description: [styles.googleSearchText],
separator: [styles.googleSearchSeparator],
+ container: [styles.mh100],
}}
numberOfLines={2}
isRowScrollable={false}
@@ -441,11 +474,13 @@ function AddressSearch(
)
}
placeholder=""
- />
- setLocationErrorCode(null)}
- locationErrorCode={locationErrorCode}
- />
+ listViewDisplayed
+ >
+ setLocationErrorCode(null)}
+ locationErrorCode={locationErrorCode}
+ />
+
{isFetchingCurrentLocation && }
diff --git a/src/components/AddressSearch/listViewOverflow/index.native.ts b/src/components/AddressSearch/listViewOverflow/index.native.ts
new file mode 100644
index 000000000000..36b9f4005376
--- /dev/null
+++ b/src/components/AddressSearch/listViewOverflow/index.native.ts
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-restricted-imports
+import {defaultStyles} from '@styles/index';
+
+export default defaultStyles.overflowHidden;
diff --git a/src/components/AddressSearch/listViewOverflow/index.ts b/src/components/AddressSearch/listViewOverflow/index.ts
new file mode 100644
index 000000000000..ae8bf35cc80c
--- /dev/null
+++ b/src/components/AddressSearch/listViewOverflow/index.ts
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-restricted-imports
+import {defaultStyles} from '@styles/index';
+
+export default defaultStyles.overflowAuto;
diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts
index bc7acf3f7e40..22cc3834b7a9 100644
--- a/src/components/AddressSearch/types.ts
+++ b/src/components/AddressSearch/types.ts
@@ -24,6 +24,10 @@ type StreetValue = {
street: string;
};
+type PredefinedPlace = Place & {
+ name?: string;
+};
+
type AddressSearchProps = {
/** The ID used to uniquely identify the input in a Form */
inputID?: string;
@@ -31,6 +35,9 @@ type AddressSearchProps = {
/** Saves a draft of the input value when used in a form */
shouldSaveDraft?: boolean;
+ /** Callback that is called when the text input is focused */
+ onFocus?: () => void;
+
/** Callback that is called when the text input is blurred */
onBlur?: () => void;
@@ -65,7 +72,7 @@ type AddressSearchProps = {
canUseCurrentLocation?: boolean;
/** A list of predefined places that can be shown when the user isn't searching for something */
- predefinedPlaces?: Place[] | null;
+ predefinedPlaces?: PredefinedPlace[] | null;
/** A map of inputID key names */
renamedInputKeys?: Address;
@@ -85,4 +92,4 @@ type AddressSearchProps = {
type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent, containerRef: RefObject) => boolean;
-export type {CurrentLocationButtonProps, AddressSearchProps, IsCurrentTargetInsideContainerType, StreetValue};
+export type {CurrentLocationButtonProps, AddressSearchProps, IsCurrentTargetInsideContainerType, StreetValue, PredefinedPlace};
diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
index 0c8af3dfc826..595e28acd3bc 100644
--- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
+++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
@@ -7,6 +7,7 @@ import {ShowContextMenuContext, showContextMenuForReport} from '@components/Show
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
+import * as Browser from '@libs/Browser';
import fileDownload from '@libs/fileDownload';
import * as ReportUtils from '@libs/ReportUtils';
import * as Download from '@userActions/Download';
@@ -48,7 +49,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow
return;
}
Download.setDownload(sourceID, true);
- fileDownload(sourceURLWithAuth, displayName).then(() => Download.setDownload(sourceID, false));
+ fileDownload(sourceURLWithAuth, displayName, '', Browser.isMobileSafari()).then(() => Download.setDownload(sourceID, false));
}}
onPressIn={onPressIn}
onPressOut={onPressOut}
diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index de98ba79d23c..fb6a8e911e87 100644
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -607,6 +607,7 @@ export default withOnyx({
const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '0' : '0';
return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
},
+ initWithStoredValues: false,
},
})(memo(AttachmentModal));
diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx
deleted file mode 100644
index 839e05c419df..000000000000
--- a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import type {StyleProp, ViewStyle} from 'react-native';
-import {PixelRatio, View} from 'react-native';
-import useThemeStyles from '@hooks/useThemeStyles';
-import useWindowDimensions from '@hooks/useWindowDimensions';
-
-type AttachmentCarouselCellRendererProps = {
- /** Cell Container styles */
- style?: StyleProp;
-};
-
-function AttachmentCarouselCellRenderer(props: AttachmentCarouselCellRendererProps) {
- const styles = useThemeStyles();
- const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
- const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true);
- const style = [props.style, styles.h100, {width: PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2)}];
-
- return (
-
- );
-}
-
-AttachmentCarouselCellRenderer.displayName = 'AttachmentCarouselCellRenderer';
-
-export default React.memo(AttachmentCarouselCellRenderer);
diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx
index 42536ae208ae..23b285faf10e 100644
--- a/src/components/Attachments/AttachmentCarousel/index.tsx
+++ b/src/components/Attachments/AttachmentCarousel/index.tsx
@@ -1,8 +1,10 @@
import isEqual from 'lodash/isEqual';
-import React, {useCallback, useEffect, useRef, useState} from 'react';
+import React, {useCallback, useEffect, useMemo, useState} from 'react';
import type {ListRenderItemInfo} from 'react-native';
-import {FlatList, Keyboard, PixelRatio, View} from 'react-native';
+import {Keyboard, PixelRatio, View} from 'react-native';
+import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import {withOnyx} from 'react-native-onyx';
+import Animated, {scrollTo, useAnimatedRef} from 'react-native-reanimated';
import type {Attachment, AttachmentSource} from '@components/Attachments/types';
import BlockingView from '@components/BlockingViews/BlockingView';
import * as Illustrations from '@components/Icon/Illustrations';
@@ -10,12 +12,12 @@ import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenCo
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import AttachmentCarouselCellRenderer from './AttachmentCarouselCellRenderer';
import CarouselActions from './CarouselActions';
import CarouselButtons from './CarouselButtons';
import CarouselItem from './CarouselItem';
@@ -29,16 +31,23 @@ const viewabilityConfig = {
itemVisiblePercentThreshold: 95,
};
+const MIN_FLING_VELOCITY = 500;
+
function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility}: AttachmentCarouselProps) {
const theme = useTheme();
const {translate} = useLocalize();
+ const {isSmallScreenWidth, windowWidth} = useWindowDimensions();
const styles = useThemeStyles();
const {isFullScreenRef} = useFullScreenContext();
- const scrollRef = useRef(null);
+ const scrollRef = useAnimatedRef>>();
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
- const [containerWidth, setContainerWidth] = useState(0);
+ const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true);
+ const cellWidth = useMemo(
+ () => PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2),
+ [modalStyles.borderWidth, modalStyles.marginHorizontal, windowWidth],
+ );
const [page, setPage] = useState(0);
const [attachments, setAttachments] = useState([]);
const [activeSource, setActiveSource] = useState(source);
@@ -51,6 +60,10 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions ?? undefined);
if (isEqual(attachments, attachmentsFromReport)) {
+ if (attachments.length === 0) {
+ setPage(-1);
+ setDownloadButtonVisibility?.(false);
+ }
return;
}
@@ -75,6 +88,17 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
}
}, [reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate]);
+ // Scroll position is affected when window width is resized, so we readjust it on width changes
+ useEffect(() => {
+ if (attachments.length === 0 || scrollRef.current == null) {
+ return;
+ }
+
+ scrollRef.current.scrollToIndex({index: page, animated: false});
+ // The hook is not supposed to run on page change, so we keep the page out of the dependencies
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [cellWidth]);
+
/** Updates the page state when the user navigates between attachments */
const updatePage = useCallback(
({viewableItems}: UpdatePageProps) => {
@@ -121,7 +145,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
scrollRef.current.scrollToIndex({index: nextIndex, animated: canUseTouchScreen});
},
- [attachments, canUseTouchScreen, isFullScreenRef, page],
+ [attachments, canUseTouchScreen, isFullScreenRef, page, scrollRef],
);
const extractItemKey = useCallback(
@@ -133,35 +157,55 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
/** Calculate items layout information to optimize scrolling performance */
const getItemLayout = useCallback(
(data: ArrayLike | null | undefined, index: number) => ({
- length: containerWidth,
- offset: containerWidth * index,
+ length: cellWidth,
+ offset: cellWidth * index,
index,
}),
- [containerWidth],
+ [cellWidth],
);
/** Defines how a single attachment should be rendered */
const renderItem = useCallback(
({item}: ListRenderItemInfo) => (
- setShouldShowArrows((oldState: boolean) => !oldState) : undefined}
- isModalHovered={shouldShowArrows}
- />
+
+ setShouldShowArrows((oldState) => !oldState) : undefined}
+ isModalHovered={shouldShowArrows}
+ />
+
),
- [activeSource, canUseTouchScreen, setShouldShowArrows, shouldShowArrows],
+ [activeSource, canUseTouchScreen, cellWidth, setShouldShowArrows, shouldShowArrows, styles.h100],
+ );
+ /** Pan gesture handing swiping through attachments on touch screen devices */
+ const pan = useMemo(
+ () =>
+ Gesture.Pan()
+ .enabled(canUseTouchScreen)
+ .onUpdate(({translationX}) => scrollTo(scrollRef, page * cellWidth - translationX, 0, false))
+ .onEnd(({translationX, velocityX}) => {
+ let newIndex;
+ if (velocityX > MIN_FLING_VELOCITY) {
+ // User flung to the right
+ newIndex = Math.max(0, page - 1);
+ } else if (velocityX < -MIN_FLING_VELOCITY) {
+ // User flung to the left
+ newIndex = Math.min(attachments.length - 1, page + 1);
+ } else {
+ // snap scroll position to the nearest cell (making sure it's within the bounds of the list)
+ const delta = Math.round(-translationX / cellWidth);
+ newIndex = Math.min(attachments.length - 1, Math.max(0, page + delta));
+ }
+
+ scrollTo(scrollRef, newIndex * cellWidth, 0, true);
+ }),
+ [attachments.length, canUseTouchScreen, cellWidth, page, scrollRef],
);
return (
{
- if (isFullScreenRef.current) {
- return;
- }
- setContainerWidth(PixelRatio.roundToNearestPixel(nativeEvent.layout.width));
- }}
onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
>
@@ -185,36 +229,26 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
cancelAutoHideArrow={cancelAutoHideArrows}
/>
- {containerWidth > 0 && (
-
+
- )}
+
>
diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx
index 156c27abbc9d..8942bf97a7dd 100644
--- a/src/components/AvatarWithDisplayName.tsx
+++ b/src/components/AvatarWithDisplayName.tsx
@@ -1,6 +1,6 @@
import React, {useCallback, useEffect, useRef} from 'react';
import {View} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -12,7 +12,7 @@ import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx';
+import type {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx';
import DisplayNames from './DisplayNames';
import MultipleAvatars from './MultipleAvatars';
import ParentNavigationSubtitle from './ParentNavigationSubtitle';
@@ -25,7 +25,7 @@ type AvatarWithDisplayNamePropsWithOnyx = {
parentReportActions: OnyxEntry;
/** Personal details of all users */
- personalDetails: OnyxEntry;
+ personalDetails: OnyxCollection;
};
type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & {
diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx
index f13e6453c6ed..0d5a825b79b5 100644
--- a/src/components/AvatarWithImagePicker.tsx
+++ b/src/components/AvatarWithImagePicker.tsx
@@ -306,7 +306,6 @@ function AvatarWithImagePicker({
-
+
{source ? (
)}
-
+
{!disabled && (
-
+
{shouldShowIcon && icon && (
) : (
diff --git a/src/components/CurrencySelectionList/index.tsx b/src/components/CurrencySelectionList/index.tsx
index 361d82140326..02ed11afa7db 100644
--- a/src/components/CurrencySelectionList/index.tsx
+++ b/src/components/CurrencySelectionList/index.tsx
@@ -48,6 +48,7 @@ function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode,
textInputValue={searchValue}
onChangeText={setSearchValue}
onSelectRow={onSelect}
+ shouldDebounceRowSelect
headerMessage={headerMessage}
initiallyFocusedOptionKey={initiallySelectedCurrencyCode}
showScrollIndicator
diff --git a/src/components/EReceiptThumbnail.tsx b/src/components/EReceiptThumbnail.tsx
index 63889f76e67c..f4216dcc9f8a 100644
--- a/src/components/EReceiptThumbnail.tsx
+++ b/src/components/EReceiptThumbnail.tsx
@@ -21,7 +21,7 @@ type EReceiptThumbnailOnyxProps = {
transaction: OnyxEntry;
};
-type IconSize = 'small' | 'medium' | 'large';
+type IconSize = 'x-small' | 'small' | 'medium' | 'large';
type EReceiptThumbnailProps = EReceiptThumbnailOnyxProps & {
/** TransactionID of the transaction this EReceipt corresponds to. It's used by withOnyx HOC */
@@ -40,7 +40,7 @@ type EReceiptThumbnailProps = EReceiptThumbnailOnyxProps & {
/** Center the eReceipt Icon vertically */
centerIconV?: boolean;
- /** Size of the eReceipt icon. Possible values 'small', 'medium' or 'large' */
+ /** Size of the eReceipt icon. Possible values 'x-small', 'small', 'medium' or 'large' */
iconSize?: IconSize;
};
@@ -72,8 +72,16 @@ function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptT
let receiptMCCSize: number = variables.eReceiptMCCHeightWidth;
let labelFontSize: number = variables.fontSizeNormal;
let labelLineHeight: number = variables.lineHeightLarge;
+ let backgroundImageMinWidth: number = variables.eReceiptBackgroundImageMinWidth;
- if (iconSize === 'small') {
+ if (iconSize === 'x-small') {
+ receiptIconWidth = variables.eReceiptIconWidthXSmall;
+ receiptIconHeight = variables.eReceiptIconHeightXSmall;
+ receiptMCCSize = variables.iconSizeXSmall;
+ labelFontSize = variables.fontSizeExtraSmall;
+ labelLineHeight = variables.lineHeightXSmall;
+ backgroundImageMinWidth = variables.w80;
+ } else if (iconSize === 'small') {
receiptIconWidth = variables.eReceiptIconWidthSmall;
receiptIconHeight = variables.eReceiptIconHeightSmall;
receiptMCCSize = variables.eReceiptMCCHeightWidthSmall;
@@ -100,7 +108,7 @@ function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptT
>
diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx
index 3d20f910dca0..b6699c0cf3f4 100644
--- a/src/components/Form/FormProvider.tsx
+++ b/src/components/Form/FormProvider.tsx
@@ -74,6 +74,9 @@ type FormProviderProps = FormProvider
/** Whether to apply flex to the submit button */
submitFlexEnabled?: boolean;
+
+ /** Whether the form container should grow or adapt to the viewable available space */
+ shouldContainerGrow?: boolean;
};
function FormProvider(
diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx
index 5c74fd466a15..e7c9dba5c77a 100644
--- a/src/components/Form/FormWrapper.tsx
+++ b/src/components/Form/FormWrapper.tsx
@@ -1,8 +1,8 @@
import React, {useCallback, useMemo, useRef} from 'react';
import type {RefObject} from 'react';
// eslint-disable-next-line no-restricted-imports
-import type {ScrollView as RNScrollView, StyleProp, View, ViewStyle} from 'react-native';
-import {Keyboard} from 'react-native';
+import type {ScrollView as RNScrollView, StyleProp, ViewStyle} from 'react-native';
+import {Keyboard, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
@@ -32,6 +32,9 @@ type FormWrapperProps = ChildrenProps &
/** Whether to apply flex to the submit button */
submitFlexEnabled?: boolean;
+ /** Whether the form container should grow or adapt to the viewable available space */
+ shouldContainerGrow?: boolean;
+
/** Server side errors keyed by microtime */
errors: FormInputErrors;
@@ -60,6 +63,7 @@ function FormWrapper({
scrollContextEnabled = false,
shouldHideFixErrorsAlert = false,
disablePressOnEnter = true,
+ shouldContainerGrow = true,
}: FormWrapperProps) {
const styles = useThemeStyles();
const formRef = useRef(null);
@@ -104,7 +108,7 @@ function FormWrapper({
ref={formContentRef}
style={[style, safeAreaPaddingBottomStyle.paddingBottom ? safeAreaPaddingBottomStyle : styles.pb5]}
>
- {children}
+ {children}
{isSubmitButtonVisible && (
@@ -164,7 +168,7 @@ function FormWrapper({
) : (
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
index 6802a4518ba1..095dcb294857 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx
@@ -18,7 +18,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) {
const attrHref = htmlAttribs.href || htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] || '';
const sourceURL = tryResolveUrlFromApiRoot(attrHref);
const fileName = FileUtils.getFileName(`${sourceURL}`);
- const thumbnailUrl = htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_URL_ATTRIBUTE];
+ const thumbnailUrl = tryResolveUrlFromApiRoot(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_URL_ATTRIBUTE]);
const width = Number(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_WIDTH_ATTRIBUTE]);
const height = Number(htmlAttribs[CONST.ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE]);
const duration = Number(htmlAttribs[CONST.ATTACHMENT_DURATION_ATTRIBUTE]);
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index c147c3735e96..cadf0f01e225 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -127,6 +127,7 @@ import Printer from '@assets/images/printer.svg';
import Profile from '@assets/images/profile.svg';
import QrCode from '@assets/images/qrcode.svg';
import QuestionMark from '@assets/images/question-mark-circle.svg';
+import ReceiptPlus from '@assets/images/receipt-plus.svg';
import ReceiptScan from '@assets/images/receipt-scan.svg';
import ReceiptSearch from '@assets/images/receipt-search.svg';
import Receipt from '@assets/images/receipt.svg';
@@ -297,6 +298,7 @@ export {
QrCode,
QuestionMark,
Receipt,
+ ReceiptPlus,
ReceiptScan,
RemoveMembers,
ReceiptSearch,
diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx
index d37e00727fa6..8c4a131284c8 100644
--- a/src/components/KYCWall/BaseKYCWall.tsx
+++ b/src/components/KYCWall/BaseKYCWall.tsx
@@ -11,7 +11,7 @@ import Navigation from '@libs/Navigation/Navigation';
import * as PaymentUtils from '@libs/PaymentUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as PaymentMethods from '@userActions/PaymentMethods';
-import * as Policy from '@userActions/Policy';
+import * as Policy from '@userActions/Policy/Policy';
import * as Wallet from '@userActions/Wallet';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
diff --git a/src/components/KeyboardAvoidingView/index.ios.tsx b/src/components/KeyboardAvoidingView/index.ios.tsx
index 171210eab7ac..a7cd767377ef 100644
--- a/src/components/KeyboardAvoidingView/index.ios.tsx
+++ b/src/components/KeyboardAvoidingView/index.ios.tsx
@@ -3,7 +3,7 @@
*/
import React from 'react';
import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native';
-import type {KeyboardAvoidingViewProps} from './types';
+import type KeyboardAvoidingViewProps from './types';
function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
diff --git a/src/components/KeyboardAvoidingView/index.tsx b/src/components/KeyboardAvoidingView/index.tsx
index c0882ae1e9cc..09ec21e5b219 100644
--- a/src/components/KeyboardAvoidingView/index.tsx
+++ b/src/components/KeyboardAvoidingView/index.tsx
@@ -3,7 +3,7 @@
*/
import React from 'react';
import {View} from 'react-native';
-import type {KeyboardAvoidingViewProps} from './types';
+import type KeyboardAvoidingViewProps from './types';
function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) {
const {behavior, contentContainerStyle, enabled, keyboardVerticalOffset, ...rest} = props;
diff --git a/src/components/KeyboardAvoidingView/types.ts b/src/components/KeyboardAvoidingView/types.ts
index 2c1ef64ced8f..48d354e8b53f 100644
--- a/src/components/KeyboardAvoidingView/types.ts
+++ b/src/components/KeyboardAvoidingView/types.ts
@@ -1,4 +1,3 @@
-import type {KeyboardAvoidingViewProps} from 'react-native';
+import {KeyboardAvoidingViewProps} from 'react-native';
-// eslint-disable-next-line import/prefer-default-export
-export type {KeyboardAvoidingViewProps};
+export default KeyboardAvoidingViewProps;
diff --git a/src/components/MenuItemGroup.tsx b/src/components/MenuItemGroup.tsx
index 8dc8586028d8..ed34778dd941 100644
--- a/src/components/MenuItemGroup.tsx
+++ b/src/components/MenuItemGroup.tsx
@@ -5,7 +5,7 @@ import useWaitForNavigation from '@hooks/useWaitForNavigation';
type MenuItemGroupContextProps = {
isExecuting: boolean;
- singleExecution: (action?: Action | undefined) => (...params: T) => void;
+ singleExecution: (action: Action) => (...params: T) => void;
waitForNavigate: ReturnType;
};
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 2393273ec957..aad801a7c259 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -86,6 +86,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
+ const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((transaction) => TransactionUtils.isReceiptBeingScanned(transaction));
const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID);
const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs);
@@ -109,13 +110,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
const shouldShowMarkAsCashButton = isDraft && allHavePendingRTERViolation;
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
- const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation;
+ const shouldShowNextStep =
+ !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation && !hasScanningReceipt;
const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy);
const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount;
- const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && shouldUseNarrowLayout);
+ const isMoreContentShown = shouldShowNextStep || hasScanningReceipt || (shouldShowAnyButton && shouldUseNarrowLayout);
const confirmPayment = (type?: PaymentMethodType | undefined) => {
if (!type) {
@@ -205,7 +207,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
// Shows border if no buttons or next steps are showing below the header
- shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation}
+ shouldShowBorderBottom={!isMoreContentShown && !allHavePendingRTERViolation}
shouldShowThreeDotsButton
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
@@ -318,6 +320,20 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
)}
+ {hasScanningReceipt && (
+
+ }
+ description={translate('iou.receiptScanInProgressDescription')}
+ shouldShowBorderBottom={false}
+ />
+ )}
{isHoldMenuVisible && requestType !== undefined && (
PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate, [allPolicies, transaction?.isFromGlobalCreate]);
+ const canModifyTaxFields = !isReadOnly && !isDistanceRequest;
+
// A flag for showing the tags field
// TODO: remove the !isTypeInvoice from this condition after BE supports tags for invoices: https://github.com/Expensify/App/issues/41281
const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists) && !isTypeInvoice, [isPolicyExpenseChat, policyTagLists, isTypeInvoice]);
- // A flag for showing tax rate
- // TODO: remove the !isTypeInvoice from this condition after BE supports tax for invoices: https://github.com/Expensify/App/issues/41281
- const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy) && !isTypeInvoice;
+ const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest) && !isTypeInvoice;
// A flag for showing the billable field
const shouldShowBillable = policy?.disabledFields?.defaultBillable === false;
@@ -1024,14 +1024,14 @@ function MoneyRequestConfirmationList({
item: (
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
disabled={didConfirm}
- interactive={!isReadOnly}
+ interactive={canModifyTaxFields}
/>
),
shouldShow: shouldShowTax,
@@ -1041,14 +1041,14 @@ function MoneyRequestConfirmationList({
item: (
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
disabled={didConfirm}
- interactive={!isReadOnly}
+ interactive={canModifyTaxFields}
/>
),
shouldShow: shouldShowTax,
@@ -1230,6 +1230,7 @@ function MoneyRequestConfirmationList({
sections={sections}
ListItem={UserListItem}
onSelectRow={navigateToReportOrUserDetail}
+ shouldDebounceRowSelect
canSelectMultiple={false}
shouldPreventDefaultFocusOnSelectRow
footerContent={footerContent}
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 0f4a1c2d3bfe..33b47aafa15c 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -64,7 +64,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const isOnHold = TransactionUtils.isOnHold(transaction);
- const {windowWidth} = useWindowDimensions();
+ const {isSmallScreenWidth, windowWidth} = useWindowDimensions();
// Only the requestor can take delete the expense, admins can only edit it.
const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID;
@@ -179,14 +179,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
return;
}
- if (shouldUseNarrowLayout) {
+ if (isSmallScreenWidth) {
if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD) {
Navigation.goBack();
}
} else {
Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD);
}
- }, [shouldUseNarrowLayout, shouldShowHoldMenu]);
+ }, [isSmallScreenWidth, shouldShowHoldMenu]);
const handleHoldRequestClose = () => {
IOU.setShownHoldUseExplanation();
@@ -259,7 +259,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
cancelText={translate('common.cancel')}
danger
/>
- {shouldUseNarrowLayout && shouldShowHoldMenu && (
+ {isSmallScreenWidth && shouldShowHoldMenu && (
);
}
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 139b5fde58b2..81d59d750284 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -12,7 +12,6 @@ import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
@@ -35,7 +34,6 @@ function MoneyReportView({report, policy}: MoneyReportViewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
- const {isSmallScreenWidth} = useWindowDimensions();
const isSettled = ReportUtils.isSettled(report.reportID);
const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy);
@@ -60,113 +58,111 @@ function MoneyReportView({report, policy}: MoneyReportViewProps) {
}, [policy, report]);
return (
-
+
-
- {!ReportUtils.isClosedExpenseReportWithNoExpenses(report) && (
- <>
- {ReportUtils.reportFieldsEnabled(report) &&
- sortedPolicyReportFields.map((reportField) => {
- const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField);
- const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue;
- const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy);
- const fieldKey = ReportUtils.getReportFieldKey(reportField.fieldID);
+ {!ReportUtils.isClosedExpenseReportWithNoExpenses(report) && (
+ <>
+ {ReportUtils.reportFieldsEnabled(report) &&
+ sortedPolicyReportFields.map((reportField) => {
+ const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField);
+ const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue;
+ const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy);
+ const fieldKey = ReportUtils.getReportFieldKey(reportField.fieldID);
- return (
- reportActions.clearReportFieldErrors(report.reportID, reportField)}
- >
- Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))}
- shouldShowRightIcon
- disabled={isFieldDisabled}
- wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
- shouldGreyOutWhenDisabled={false}
- numberOfLinesTitle={0}
- interactive
- shouldStackHorizontally={false}
- onSecondaryInteraction={() => {}}
- hoverAndPressStyle={false}
- titleWithTooltips={[]}
- />
-
- );
- })}
-
-
-
- {translate('common.total')}
-
-
-
- {isSettled && !isPartiallyPaid && (
-
-
-
- )}
- reportActions.clearReportFieldErrors(report.reportID, reportField)}
>
- {formattedTotalAmount}
-
-
+ Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))}
+ shouldShowRightIcon
+ disabled={isFieldDisabled}
+ wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
+ shouldGreyOutWhenDisabled={false}
+ numberOfLinesTitle={0}
+ interactive
+ shouldStackHorizontally={false}
+ onSecondaryInteraction={() => {}}
+ hoverAndPressStyle={false}
+ titleWithTooltips={[]}
+ />
+
+ );
+ })}
+
+
+
+ {translate('common.total')}
+
- {Boolean(shouldShowBreakdown) && (
- <>
-
-
-
- {translate('cardTransactions.outOfPocket')}
-
-
-
-
- {formattedOutOfPocketAmount}
-
-
+
+ {isSettled && !isPartiallyPaid && (
+
+
+
+ )}
+
+ {formattedTotalAmount}
+
+
+
+ {Boolean(shouldShowBreakdown) && (
+ <>
+
+
+
+ {translate('cardTransactions.outOfPocket')}
+
-
-
-
- {translate('cardTransactions.companySpend')}
-
-
-
-
- {formattedCompanySpendAmount}
-
-
+
+
+ {formattedOutOfPocketAmount}
+
+
+
+
+
+
+ {translate('cardTransactions.companySpend')}
+
- >
- )}
- >
- )}
-
+
+
+ {formattedCompanySpendAmount}
+
+
+
+ >
+ )}
+ >
+ )}
);
}
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 37836580b740..d6e26d7d4346 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -15,12 +15,10 @@ import ViolationMessages from '@components/ViolationMessages';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
-import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useViolations from '@hooks/useViolations';
import type {ViolationField} from '@hooks/useViolations';
-import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CardUtils from '@libs/CardUtils';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import type {MileageRate} from '@libs/DistanceRequestUtils';
@@ -97,9 +95,7 @@ function MoneyRequestView({
const theme = useTheme();
const styles = useThemeStyles();
const session = useSession();
- const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();
- const {isSmallScreenWidth} = useWindowDimensions();
const {translate, toLocaleDigit} = useLocalize();
const parentReportAction = parentReportActions?.[report.parentReportActionID ?? ''] ?? null;
const isTrackExpense = ReportUtils.isTrackExpenseReport(report);
@@ -141,6 +137,8 @@ function MoneyRequestView({
// Used for non-restricted fields such as: description, category, tag, billable, etc.
const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction);
+ const canEditTaxFields = canEdit && !isDistanceRequest;
+
const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT);
const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT);
const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE);
@@ -148,8 +146,7 @@ function MoneyRequestView({
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
const didRceiptScanSucceed = hasReceipt && TransactionUtils.didRceiptScanSucceed(transaction);
- // TODO: remove the !isTrackExpense from this condition after this fix: https://github.com/Expensify/Expensify/issues/382786
- const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE) && !isTrackExpense;
+ const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE);
const isAdmin = policy?.role === 'admin';
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;
@@ -170,8 +167,7 @@ function MoneyRequestView({
const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists));
const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true));
- // A flag for showing tax rate
- const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy);
+ const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest);
const {getViolationsForField} = useViolations(transactionViolations ?? []);
const hasViolations = useCallback(
@@ -323,9 +319,9 @@ function MoneyRequestView({
const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && ReportUtils.isPaidGroupPolicy(report);
return (
-
+
{shouldShowAnimatedBackground && }
-
+ <>
{!isInvoice && (
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))
@@ -523,8 +519,8 @@ function MoneyRequestView({
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '', report.reportID))
@@ -553,7 +549,7 @@ function MoneyRequestView({
/>
)}
-
+ >
);
}
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index b4dcc9a15d75..699ae0907f31 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -15,6 +15,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import SelectionList from './SelectionList';
import SearchTableHeader from './SelectionList/SearchTableHeader';
+import type {ReportListItemType, TransactionListItemType} from './SelectionList/types';
import TableListItemSkeleton from './Skeletons/TableListItemSkeleton';
type SearchProps = {
@@ -22,6 +23,11 @@ type SearchProps = {
policyIDs?: string;
};
+function isReportListItemType(item: TransactionListItemType | ReportListItemType): item is ReportListItemType {
+ const reportListItem = item as ReportListItemType;
+ return reportListItem.transactions !== undefined;
+}
+
function Search({query, policyIDs}: SearchProps) {
const {isOffline} = useNetwork();
const styles = useThemeStyles();
@@ -74,10 +80,11 @@ function Search({query, policyIDs}: SearchProps) {
}
const ListItem = SearchUtils.getListItem(type);
+
const data = SearchUtils.getSections(searchResults?.data ?? {}, type);
return (
-
customListHeader={}
// To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling,
// we have configured a larger windowSize and a longer delay between batch renders.
@@ -93,8 +100,11 @@ function Search({query, policyIDs}: SearchProps) {
ListItem={ListItem}
sections={[{data, isDisabled: false}]}
onSelectRow={(item) => {
- openReport(item.transactionThreadReportID);
+ const reportID = isReportListItemType(item) ? item.reportID : item.transactionThreadReportID;
+
+ openReport(reportID);
}}
+ shouldDebounceRowSelect
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
containerStyle={[styles.pv0]}
diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx
index 6f62f8642b45..414060efc614 100644
--- a/src/components/SelectionList/BaseSelectionList.tsx
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -1,4 +1,5 @@
import {useFocusEffect, useIsFocused} from '@react-navigation/native';
+import lodashDebounce from 'lodash/debounce';
import isEmpty from 'lodash/isEmpty';
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
@@ -35,6 +36,7 @@ function BaseSelectionList(
ListItem,
canSelectMultiple = false,
onSelectRow,
+ shouldDebounceRowSelect = false,
onCheckboxPress,
onSelectAll,
onDismissError,
@@ -261,6 +263,9 @@ function BaseSelectionList(
isFocused,
});
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const debouncedOnSelectRow = useCallback(lodashDebounce(onSelectRow, 1000, {leading: true}), [onSelectRow]);
+
/**
* Logic to run when a row is selected, either with click/press or keyboard hotkeys.
*
@@ -282,7 +287,11 @@ function BaseSelectionList(
}
}
- onSelectRow(item);
+ if (shouldDebounceRowSelect) {
+ debouncedOnSelectRow(item);
+ } else {
+ onSelectRow(item);
+ }
if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow && innerTextInputRef.current) {
innerTextInputRef.current.focus();
@@ -307,6 +316,11 @@ function BaseSelectionList(
selectRow(focusedOption);
};
+ // This debounce happens on the trailing edge because on repeated enter presses, rapid component state update cancels the existing debounce and the redundant
+ // enter presses runs the debounced function again.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const debouncedSelectFocusedOption = useCallback(lodashDebounce(selectFocusedOption, 100), [selectFocusedOption]);
+
/**
* This function is used to compute the layout of any given item in our list.
* We need to implement it so that we can programmatically scroll to items outside the virtual render window of the SectionList.
@@ -460,6 +474,26 @@ function BaseSelectionList(
[scrollToIndex, setFocusedIndex],
);
+ useEffect(() => {
+ if (!(shouldDebounceRowSelect && debouncedOnSelectRow.cancel)) {
+ return;
+ }
+
+ return () => {
+ debouncedOnSelectRow.cancel();
+ };
+ }, [debouncedOnSelectRow, shouldDebounceRowSelect]);
+
+ useEffect(() => {
+ if (!(shouldDebounceRowSelect && debouncedSelectFocusedOption.cancel)) {
+ return;
+ }
+
+ return () => {
+ debouncedSelectFocusedOption.cancel();
+ };
+ }, [debouncedSelectFocusedOption, shouldDebounceRowSelect]);
+
/** Focuses the text input when the component comes into focus and after any navigation animations finish. */
useFocusEffect(
useCallback(() => {
@@ -549,7 +583,7 @@ function BaseSelectionList(
useImperativeHandle(ref, () => ({scrollAndHighlightItem}), [scrollAndHighlightItem]);
/** Selects row when pressing Enter */
- useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, {
+ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, shouldDebounceRowSelect ? debouncedSelectFocusedOption : selectFocusedOption, {
captureOnInputs: true,
shouldBubble: !flattenedSections.allOptions[focusedIndex],
shouldStopPropagation,
@@ -609,7 +643,7 @@ function BaseSelectionList(
selectTextOnFocus
spellCheck={false}
iconLeft={textInputIconLeft}
- onSubmitEditing={selectFocusedOption}
+ onSubmitEditing={shouldDebounceRowSelect ? debouncedSelectFocusedOption : selectFocusedOption}
blurOnSubmit={!!flattenedSections.allOptions.length}
isLoading={isLoadingNewOptions}
testID="selection-list-text-input"
diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
new file mode 100644
index 000000000000..9b2188c659e5
--- /dev/null
+++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
@@ -0,0 +1,54 @@
+import React, {memo} from 'react';
+import {View} from 'react-native';
+import Button from '@components/Button';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import type {SearchAccountDetails} from '@src/types/onyx/SearchResults';
+import UserInfoCell from './UserInfoCell';
+
+type ExpenseItemHeaderNarrowProps = {
+ participantFrom: SearchAccountDetails;
+ participantTo: SearchAccountDetails;
+ buttonText: string;
+ onButtonPress: () => void;
+};
+
+function ExpenseItemHeaderNarrow({participantFrom, participantTo, buttonText, onButtonPress}: ExpenseItemHeaderNarrowProps) {
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
+ const theme = useTheme();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default memo(ExpenseItemHeaderNarrow);
diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx
new file mode 100644
index 000000000000..ecab5a3053df
--- /dev/null
+++ b/src/components/SelectionList/Search/ReportListItem.tsx
@@ -0,0 +1,162 @@
+import React from 'react';
+import {View} from 'react-native';
+import Button from '@components/Button';
+import BaseListItem from '@components/SelectionList/BaseListItem';
+import type {ListItem, ReportListItemProps, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
+import Text from '@components/Text';
+import TextWithTooltip from '@components/TextWithTooltip';
+import useLocalize from '@hooks/useLocalize';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import {getSearchParams} from '@libs/SearchUtils';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+import ExpenseItemHeaderNarrow from './ExpenseItemHeaderNarrow';
+import TransactionListItem from './TransactionListItem';
+import TransactionListItemRow from './TransactionListItemRow';
+
+function ReportListItem({
+ item,
+ isFocused,
+ showTooltip,
+ isDisabled,
+ canSelectMultiple,
+ onSelectRow,
+ onDismissError,
+ shouldPreventDefaultFocusOnSelectRow,
+ onFocus,
+ shouldSyncFocus,
+}: ReportListItemProps) {
+ const reportItem = item as unknown as ReportListItemType;
+
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const {isLargeScreenWidth} = useWindowDimensions();
+ const StyleUtils = useStyleUtils();
+
+ const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];
+
+ const handleOnButtonPress = () => {
+ onSelectRow(item);
+ };
+
+ const openReportInRHP = (transactionItem: TransactionListItemType) => {
+ const searchParams = getSearchParams();
+ const currentQuery = searchParams && `query` in searchParams ? (searchParams?.query as string) : CONST.TAB_SEARCH.ALL;
+ Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(currentQuery, transactionItem.transactionThreadReportID));
+ };
+
+ const totalCell = (
+
+ );
+
+ const actionCell = (
+
+ );
+
+ if (!reportItem?.reportName && reportItem.transactions.length > 1) {
+ return null;
+ }
+
+ const participantFrom = reportItem.transactions[0].from;
+ const participantTo = reportItem.transactions[0].to;
+
+ if (reportItem.transactions.length === 1) {
+ const transactionItem = reportItem.transactions[0];
+
+ return (
+ openReportInRHP(transactionItem)}
+ onDismissError={onDismissError}
+ shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
+ onFocus={onFocus}
+ shouldSyncFocus={shouldSyncFocus}
+ />
+ );
+ }
+
+ return (
+
+ {(hovered?: boolean) => (
+
+ {!isLargeScreenWidth && (
+
+ )}
+
+
+
+
+ {reportItem?.reportName}
+ {`${reportItem.transactions.length} ${translate('search.groupedExpenses')}`}
+
+
+ {totalCell}
+
+ {/** styles.reportListItemActionButtonMargin added here to move the action button by the type column distance */}
+ {isLargeScreenWidth && (
+ {actionCell}
+ )}
+
+
+ {reportItem.transactions.map((transaction) => (
+ {
+ openReportInRHP(transaction);
+ }}
+ showItemHeaderOnNarrowLayout={false}
+ containerStyle={styles.mt3}
+ isHovered={hovered}
+ />
+ ))}
+
+ )}
+
+ );
+}
+
+ReportListItem.displayName = 'ReportListItem';
+
+export default ReportListItem;
diff --git a/src/components/SelectionList/TextWithIconCell.tsx b/src/components/SelectionList/Search/TextWithIconCell.tsx
similarity index 100%
rename from src/components/SelectionList/TextWithIconCell.tsx
rename to src/components/SelectionList/Search/TextWithIconCell.tsx
diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx
new file mode 100644
index 000000000000..b222631b7273
--- /dev/null
+++ b/src/components/SelectionList/Search/TransactionListItem.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import BaseListItem from '@components/SelectionList/BaseListItem';
+import type {ListItem, TransactionListItemProps, TransactionListItemType} from '@components/SelectionList/types';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import TransactionListItemRow from './TransactionListItemRow';
+
+function TransactionListItem({
+ item,
+ isFocused,
+ showTooltip,
+ isDisabled,
+ canSelectMultiple,
+ onSelectRow,
+ onDismissError,
+ shouldPreventDefaultFocusOnSelectRow,
+ onFocus,
+ shouldSyncFocus,
+}: TransactionListItemProps) {
+ const transactionItem = item as unknown as TransactionListItemType;
+ const styles = useThemeStyles();
+
+ const {isLargeScreenWidth} = useWindowDimensions();
+
+ const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];
+
+ const listItemWrapperStyle = [
+ styles.flex1,
+ styles.userSelectNone,
+ isLargeScreenWidth ? {...styles.flexRow, ...styles.justifyContentBetween, ...styles.alignItemsCenter} : {...styles.flexColumn, ...styles.alignItemsStretch},
+ ];
+
+ return (
+
+ {(hovered?: boolean) => (
+ {
+ onSelectRow(item);
+ }}
+ isHovered={hovered}
+ />
+ )}
+
+ );
+}
+
+TransactionListItem.displayName = 'TransactionListItem';
+
+export default TransactionListItem;
diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx
new file mode 100644
index 000000000000..71912c2d3c7a
--- /dev/null
+++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx
@@ -0,0 +1,435 @@
+import React, {memo} from 'react';
+import type {StyleProp, ViewStyle} from 'react-native';
+import {View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
+import Avatar from '@components/Avatar';
+import Button from '@components/Button';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import ReceiptImage from '@components/ReceiptImage';
+import type {
+ ActionCellProps,
+ CellProps,
+ CurrencyCellProps,
+ DateCellProps,
+ MerchantCellProps,
+ ReceiptCellProps,
+ TransactionCellProps,
+ TransactionListItemType,
+ TypeCellProps,
+ UserCellProps,
+} from '@components/SelectionList/types';
+import Text from '@components/Text';
+import TextWithTooltip from '@components/TextWithTooltip';
+import useLocalize from '@hooks/useLocalize';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
+import * as TransactionUtils from '@libs/TransactionUtils';
+import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import type {Transaction} from '@src/types/onyx';
+import type {SearchTransactionType} from '@src/types/onyx/SearchResults';
+import ExpenseItemHeaderNarrow from './ExpenseItemHeaderNarrow';
+import TextWithIconCell from './TextWithIconCell';
+
+type TransactionListItemRowProps = {
+ item: TransactionListItemType;
+ showTooltip: boolean;
+ onButtonPress: () => void;
+ showItemHeaderOnNarrowLayout?: boolean;
+ containerStyle?: StyleProp;
+ isHovered?: boolean;
+};
+
+const getTypeIcon = (type?: SearchTransactionType) => {
+ switch (type) {
+ case CONST.SEARCH_TRANSACTION_TYPE.CASH:
+ return Expensicons.Cash;
+ case CONST.SEARCH_TRANSACTION_TYPE.CARD:
+ return Expensicons.CreditCard;
+ case CONST.SEARCH_TRANSACTION_TYPE.DISTANCE:
+ return Expensicons.Car;
+ default:
+ return Expensicons.Cash;
+ }
+};
+
+function arePropsEqual(prevProps: CellProps, nextProps: CellProps) {
+ return prevProps.keyForList === nextProps.keyForList;
+}
+
+function areReceiptPropsEqual(prevProps: ReceiptCellProps, nextProps: ReceiptCellProps) {
+ return prevProps.keyForList === nextProps.keyForList && prevProps.isHovered === nextProps.isHovered;
+}
+
+const ReceiptCell = memo(({transactionItem, isHovered = false}: ReceiptCellProps) => {
+ const theme = useTheme();
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
+
+ return (
+
+
+
+ );
+}, areReceiptPropsEqual);
+
+const DateCell = memo(({showTooltip, date, isLargeScreenWidth}: DateCellProps) => {
+ const styles = useThemeStyles();
+
+ return (
+
+ );
+}, arePropsEqual);
+
+const MerchantCell = memo(({showTooltip, transactionItem, merchant, description}: MerchantCellProps) => {
+ const styles = useThemeStyles();
+ return (
+
+ );
+}, arePropsEqual);
+
+const UserCell = memo(({participant}: UserCellProps) => {
+ const styles = useThemeStyles();
+
+ const displayName = participant?.name ?? participant?.displayName ?? participant?.login;
+ const avatarURL = participant?.avatarURL ?? participant?.avatar;
+ const isWorkspace = participant?.avatarURL !== undefined;
+ const iconType = isWorkspace ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR;
+
+ return (
+
+
+
+ {displayName}
+
+
+ );
+}, arePropsEqual);
+
+const TotalCell = memo(({showTooltip, amount, currency, isLargeScreenWidth}: CurrencyCellProps) => {
+ const styles = useThemeStyles();
+
+ return (
+
+ );
+}, arePropsEqual);
+
+const TypeCell = memo(({typeIcon, isLargeScreenWidth}: TypeCellProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+}, arePropsEqual);
+
+const ActionCell = memo(({item, onSelectRow}: ActionCellProps) => {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+
+ return (
+