diff --git a/android/app/build.gradle b/android/app/build.gradle
index bebbda72c856..db5c94a23b37 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -90,8 +90,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001038400
- versionName "1.3.84-0"
+ versionCode 1001038401
+ versionName "1.3.84-1"
}
flavorDimensions "default"
diff --git a/docs/assets/Files/Hosting b/docs/assets/Files/Hosting
new file mode 100644
index 000000000000..ad2a361edc03
--- /dev/null
+++ b/docs/assets/Files/Hosting
@@ -0,0 +1 @@
+Holding tank for help.expensify.com support files
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 32506cc25a89..ff61565e94a3 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.3.84.0
+ 1.3.84.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 8e2c10ee1c71..a0b4820ba663 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.3.84.0
+ 1.3.84.1
diff --git a/package-lock.json b/package-lock.json
index 4030bc26bf7e..71224c2b7b90 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.3.84-0",
+ "version": "1.3.84-1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.3.84-0",
+ "version": "1.3.84-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index aa0875865393..2e865f407424 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.3.84-0",
+ "version": "1.3.84-1",
"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.",
diff --git a/src/components/TabSelector/TabSelectorItem.js b/src/components/TabSelector/TabSelectorItem.js
index 6611b8acf914..04a576f9dbf0 100644
--- a/src/components/TabSelector/TabSelectorItem.js
+++ b/src/components/TabSelector/TabSelectorItem.js
@@ -54,13 +54,13 @@ function TabSelectorItem({icon, title, onPress, backgroundColor, activeOpacity,
)}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 4f96314c4efa..b321903a9781 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -479,8 +479,8 @@ export default {
sidebarScreen: {
buttonSearch: 'Search',
buttonMySettings: 'My settings',
- fabNewChat: 'Send message',
- fabNewChatExplained: 'Send message (Floating action)',
+ fabNewChat: 'Start chat',
+ fabNewChatExplained: 'Start chat (Floating action)',
chatPinned: 'Chat pinned',
draftedMessage: 'Drafted message',
listOfChatMessages: 'List of chat messages',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index cd585ae88e7e..51d9923a570b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -471,8 +471,8 @@ export default {
sidebarScreen: {
buttonSearch: 'Buscar',
buttonMySettings: 'Mi configuraciĆ³n',
- fabNewChat: 'Enviar mensaje',
- fabNewChatExplained: 'Enviar mensaje',
+ fabNewChat: 'Iniciar chat',
+ fabNewChatExplained: 'Iniciar chat',
chatPinned: 'Chat fijado',
draftedMessage: 'Mensaje borrador',
listOfChatMessages: 'Lista de mensajes del chat',
diff --git a/src/libs/Performance.js b/src/libs/Performance.tsx
similarity index 52%
rename from src/libs/Performance.js
rename to src/libs/Performance.tsx
index 0207fd20c564..cfb5e258c9f8 100644
--- a/src/libs/Performance.js
+++ b/src/libs/Performance.tsx
@@ -1,39 +1,73 @@
-import _ from 'underscore';
-import lodashTransform from 'lodash/transform';
import React, {Profiler, forwardRef} from 'react';
import {Alert, InteractionManager} from 'react-native';
+import lodashTransform from 'lodash/transform';
+import isObject from 'lodash/isObject';
+import isEqual from 'lodash/isEqual';
+import {Performance as RNPerformance, PerformanceEntry, PerformanceMark, PerformanceMeasure} from 'react-native-performance';
+import {PerformanceObserverEntryList} from 'react-native-performance/lib/typescript/performance-observer';
import * as Metrics from './Metrics';
import getComponentDisplayName from './getComponentDisplayName';
import CONST from '../CONST';
import isE2ETestSession from './E2E/isE2ETestSession';
-/** @type {import('react-native-performance').Performance} */
-let rnPerformance;
+type WrappedComponentConfig = {id: string};
+
+type PerformanceEntriesCallback = (entry: PerformanceEntry) => void;
+
+type Phase = 'mount' | 'update';
+
+type WithRenderTraceHOC =
>(WrappedComponent: React.ComponentType
) => React.ComponentType
>;
+
+type BlankHOC =
>(Component: React.ComponentType
) => React.ComponentType
;
+
+type SetupPerformanceObserver = () => void;
+type DiffObject = (object: Record, base: Record) => Record;
+type GetPerformanceMetrics = () => PerformanceEntry[];
+type PrintPerformanceMetrics = () => void;
+type MarkStart = (name: string, detail?: Record) => PerformanceMark | void;
+type MarkEnd = (name: string, detail?: Record) => PerformanceMark | void;
+type MeasureFailSafe = (measureName: string, startOrMeasureOptions: string, endMark: string) => void;
+type MeasureTTI = (endMark: string) => void;
+type TraceRender = (id: string, phase: Phase, actualDuration: number, baseDuration: number, startTime: number, commitTime: number, interactions: Set) => PerformanceMeasure | void;
+type WithRenderTrace = ({id}: WrappedComponentConfig) => WithRenderTraceHOC | BlankHOC;
+type SubscribeToMeasurements = (callback: PerformanceEntriesCallback) => void;
+
+type PerformanceModule = {
+ diffObject: DiffObject;
+ setupPerformanceObserver: SetupPerformanceObserver;
+ getPerformanceMetrics: GetPerformanceMetrics;
+ printPerformanceMetrics: PrintPerformanceMetrics;
+ markStart: MarkStart;
+ markEnd: MarkEnd;
+ measureFailSafe: MeasureFailSafe;
+ measureTTI: MeasureTTI;
+ traceRender: TraceRender;
+ withRenderTrace: WithRenderTrace;
+ subscribeToMeasurements: SubscribeToMeasurements;
+};
+
+let rnPerformance: RNPerformance;
/**
* Deep diff between two objects. Useful for figuring out what changed about an object from one render to the next so
* that state and props updates can be optimized.
- *
- * @param {Object} object
- * @param {Object} base
- * @return {Object}
*/
-function diffObject(object, base) {
- function changes(obj, comparisonObject) {
+function diffObject(object: Record, base: Record): Record {
+ function changes(obj: Record, comparisonObject: Record): Record {
return lodashTransform(obj, (result, value, key) => {
- if (_.isEqual(value, comparisonObject[key])) {
+ if (isEqual(value, comparisonObject[key])) {
return;
}
// eslint-disable-next-line no-param-reassign
- result[key] = _.isObject(value) && _.isObject(comparisonObject[key]) ? changes(value, comparisonObject[key]) : value;
+ result[key] = isObject(value) && isObject(comparisonObject[key]) ? changes(value as Record, comparisonObject[key] as Record) : value;
});
}
return changes(object, base);
}
-const Performance = {
+const Performance: PerformanceModule = {
// When performance monitoring is disabled the implementations are blank
diffObject,
setupPerformanceObserver: () => {},
@@ -44,7 +78,11 @@ const Performance = {
measureFailSafe: () => {},
measureTTI: () => {},
traceRender: () => {},
- withRenderTrace: () => (Component) => Component,
+ withRenderTrace:
+ () =>
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ >(Component: React.ComponentType
): React.ComponentType
=>
+ Component,
subscribeToMeasurements: () => {},
};
@@ -53,20 +91,21 @@ if (Metrics.canCapturePerformanceMetrics()) {
perfModule.setResourceLoggingEnabled(true);
rnPerformance = perfModule.default;
- Performance.measureFailSafe = (measureName, startOrMeasureOptions, endMark) => {
+ Performance.measureFailSafe = (measureName: string, startOrMeasureOptions: string, endMark: string) => {
try {
rnPerformance.measure(measureName, startOrMeasureOptions, endMark);
} catch (error) {
// Sometimes there might be no start mark recorded and the measure will fail with an error
- console.debug(error.message);
+ if (error instanceof Error) {
+ console.debug(error.message);
+ }
}
};
/**
* Measures the TTI time. To be called when the app is considered to be interactive.
- * @param {String} [endMark] Optional end mark name
*/
- Performance.measureTTI = (endMark) => {
+ Performance.measureTTI = (endMark: string) => {
// Make sure TTI is captured when the app is really usable
InteractionManager.runAfterInteractions(() => {
requestAnimationFrame(() => {
@@ -88,8 +127,8 @@ if (Metrics.canCapturePerformanceMetrics()) {
performanceReported.setupDefaultFlipperReporter();
// Monitor some native marks that we want to put on the timeline
- new perfModule.PerformanceObserver((list, observer) => {
- list.getEntries().forEach((entry) => {
+ new perfModule.PerformanceObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => {
+ list.getEntries().forEach((entry: PerformanceEntry) => {
if (entry.name === 'nativeLaunchEnd') {
Performance.measureFailSafe('nativeLaunch', 'nativeLaunchStart', 'nativeLaunchEnd');
}
@@ -108,8 +147,8 @@ if (Metrics.canCapturePerformanceMetrics()) {
}).observe({type: 'react-native-mark', buffered: true});
// Monitor for "_end" marks and capture "_start" to "_end" measures
- new perfModule.PerformanceObserver((list) => {
- list.getEntriesByType('mark').forEach((mark) => {
+ new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => {
+ list.getEntriesByType('mark').forEach((mark: PerformanceEntry) => {
if (mark.name.endsWith('_end')) {
const end = mark.name;
const name = end.replace(/_end$/, '');
@@ -125,65 +164,64 @@ if (Metrics.canCapturePerformanceMetrics()) {
}).observe({type: 'mark', buffered: true});
};
- Performance.getPerformanceMetrics = () =>
- _.chain([
+ Performance.getPerformanceMetrics = (): PerformanceEntry[] =>
+ [
...rnPerformance.getEntriesByName('nativeLaunch'),
...rnPerformance.getEntriesByName('runJsBundle'),
...rnPerformance.getEntriesByName('jsBundleDownload'),
...rnPerformance.getEntriesByName('TTI'),
...rnPerformance.getEntriesByName('regularAppStart'),
...rnPerformance.getEntriesByName('appStartedToReady'),
- ])
- .filter((entry) => entry.duration > 0)
- .value();
+ ].filter((entry) => entry.duration > 0);
/**
* Outputs performance stats. We alert these so that they are easy to access in release builds.
*/
Performance.printPerformanceMetrics = () => {
const stats = Performance.getPerformanceMetrics();
- const statsAsText = _.map(stats, (entry) => `\u2022 ${entry.name}: ${entry.duration.toFixed(1)}ms`).join('\n');
+ const statsAsText = stats.map((entry) => `\u2022 ${entry.name}: ${entry.duration.toFixed(1)}ms`).join('\n');
if (stats.length > 0) {
Alert.alert('Performance', statsAsText);
}
};
- Performance.subscribeToMeasurements = (callback) => {
- new perfModule.PerformanceObserver((list) => {
+ Performance.subscribeToMeasurements = (callback: PerformanceEntriesCallback) => {
+ new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => {
list.getEntriesByType('measure').forEach(callback);
}).observe({type: 'measure', buffered: true});
};
/**
* Add a start mark to the performance entries
- * @param {string} name
- * @param {Object} [detail]
- * @returns {PerformanceMark}
*/
- Performance.markStart = (name, detail) => rnPerformance.mark(`${name}_start`, {detail});
+ Performance.markStart = (name: string, detail?: Record): PerformanceMark => rnPerformance.mark(`${name}_start`, {detail});
/**
* Add an end mark to the performance entries
* A measure between start and end is captured automatically
- * @param {string} name
- * @param {Object} [detail]
- * @returns {PerformanceMark}
*/
- Performance.markEnd = (name, detail) => rnPerformance.mark(`${name}_end`, {detail});
+ Performance.markEnd = (name: string, detail?: Record): PerformanceMark => rnPerformance.mark(`${name}_end`, {detail});
/**
* Put data emitted by Profiler components on the timeline
- * @param {string} id the "id" prop of the Profiler tree that has just committed
- * @param {'mount'|'update'} phase either "mount" (if the tree just mounted) or "update" (if it re-rendered)
- * @param {number} actualDuration time spent rendering the committed update
- * @param {number} baseDuration estimated time to render the entire subtree without memoization
- * @param {number} startTime when React began rendering this update
- * @param {number} commitTime when React committed this update
- * @param {Set} interactions the Set of interactions belonging to this update
- * @returns {PerformanceMeasure}
+ * @param id the "id" prop of the Profiler tree that has just committed
+ * @param phase either "mount" (if the tree just mounted) or "update" (if it re-rendered)
+ * @param actualDuration time spent rendering the committed update
+ * @param baseDuration estimated time to render the entire subtree without memoization
+ * @param startTime when React began rendering this update
+ * @param commitTime when React committed this update
+ * @param interactions the Set of interactions belonging to this update
*/
- Performance.traceRender = (id, phase, actualDuration, baseDuration, startTime, commitTime, interactions) =>
+ Performance.traceRender = (
+ id: string,
+ phase: Phase,
+ actualDuration: number,
+ baseDuration: number,
+ startTime: number,
+ commitTime: number,
+ interactions: Set,
+ ): PerformanceMeasure =>
rnPerformance.measure(id, {
start: startTime,
duration: actualDuration,
@@ -197,14 +235,12 @@ if (Metrics.canCapturePerformanceMetrics()) {
/**
* A HOC that captures render timings of the Wrapped component
- * @param {object} config
- * @param {string} config.id
- * @returns {function(React.Component): React.FunctionComponent}
*/
Performance.withRenderTrace =
- ({id}) =>
- (WrappedComponent) => {
- const WithRenderTrace = forwardRef((props, ref) => (
+ ({id}: WrappedComponentConfig) =>
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ >(WrappedComponent: React.ComponentType
): React.ComponentType
> => {
+ const WithRenderTrace: React.ComponentType
> = forwardRef((props: P, ref) => (
));
- WithRenderTrace.displayName = `withRenderTrace(${getComponentDisplayName(WrappedComponent)})`;
+ WithRenderTrace.displayName = `withRenderTrace(${getComponentDisplayName(WrappedComponent as React.ComponentType)})`;
return WithRenderTrace;
};
}
diff --git a/src/libs/localFileDownload/index.android.js b/src/libs/localFileDownload/index.android.ts
similarity index 88%
rename from src/libs/localFileDownload/index.android.js
rename to src/libs/localFileDownload/index.android.ts
index b3e39e7a7a53..ad13b5c5cfa7 100644
--- a/src/libs/localFileDownload/index.android.js
+++ b/src/libs/localFileDownload/index.android.ts
@@ -1,15 +1,13 @@
import RNFetchBlob from 'react-native-blob-util';
import * as FileUtils from '../fileDownload/FileUtils';
+import LocalFileDownload from './types';
/**
* Writes a local file to the app's internal directory with the given fileName
* and textContent, so we're able to copy it to the Android public download dir.
* After the file is copied, it is removed from the internal dir.
- *
- * @param {String} fileName
- * @param {String} textContent
*/
-export default function localFileDownload(fileName, textContent) {
+const localFileDownload: LocalFileDownload = (fileName, textContent) => {
const newFileName = FileUtils.appendTimeToFileName(fileName);
const dir = RNFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${newFileName}.txt`;
@@ -34,4 +32,6 @@ export default function localFileDownload(fileName, textContent) {
RNFetchBlob.fs.unlink(path);
});
});
-}
+};
+
+export default localFileDownload;
diff --git a/src/libs/localFileDownload/index.ios.js b/src/libs/localFileDownload/index.ios.ts
similarity index 82%
rename from src/libs/localFileDownload/index.ios.js
rename to src/libs/localFileDownload/index.ios.ts
index 1241f5a535db..3597ea5f6d3c 100644
--- a/src/libs/localFileDownload/index.ios.js
+++ b/src/libs/localFileDownload/index.ios.ts
@@ -1,16 +1,14 @@
import {Share} from 'react-native';
import RNFetchBlob from 'react-native-blob-util';
import * as FileUtils from '../fileDownload/FileUtils';
+import LocalFileDownload from './types';
/**
* Writes a local file to the app's internal directory with the given fileName
* and textContent, so we're able to share it using iOS' share API.
* After the file is shared, it is removed from the internal dir.
- *
- * @param {String} fileName
- * @param {String} textContent
*/
-export default function localFileDownload(fileName, textContent) {
+const localFileDownload: LocalFileDownload = (fileName, textContent) => {
const newFileName = FileUtils.appendTimeToFileName(fileName);
const dir = RNFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${newFileName}.txt`;
@@ -20,4 +18,6 @@ export default function localFileDownload(fileName, textContent) {
RNFetchBlob.fs.unlink(path);
});
});
-}
+};
+
+export default localFileDownload;
diff --git a/src/libs/localFileDownload/index.js b/src/libs/localFileDownload/index.ts
similarity index 77%
rename from src/libs/localFileDownload/index.js
rename to src/libs/localFileDownload/index.ts
index 427928834c9c..7b9b4973d5c1 100644
--- a/src/libs/localFileDownload/index.js
+++ b/src/libs/localFileDownload/index.ts
@@ -1,18 +1,18 @@
import * as FileUtils from '../fileDownload/FileUtils';
+import LocalFileDownload from './types';
/**
* Creates a Blob with the given fileName and textContent, then dynamically
* creates a temporary anchor, just to programmatically click it, so the file
* is downloaded by the browser.
- *
- * @param {String} fileName
- * @param {String} textContent
*/
-export default function localFileDownload(fileName, textContent) {
+const localFileDownload: LocalFileDownload = (fileName, textContent) => {
const blob = new Blob([textContent], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = FileUtils.appendTimeToFileName(`${fileName}.txt`);
link.href = url;
link.click();
-}
+};
+
+export default localFileDownload;
diff --git a/src/libs/localFileDownload/types.ts b/src/libs/localFileDownload/types.ts
new file mode 100644
index 000000000000..2086e2334d39
--- /dev/null
+++ b/src/libs/localFileDownload/types.ts
@@ -0,0 +1,3 @@
+type LocalFileDownload = (fileName: string, textContent: string) => void;
+
+export default LocalFileDownload;
diff --git a/src/styles/styles.ts b/src/styles/styles.ts
index ba3c4d888858..07fb165cd76e 100644
--- a/src/styles/styles.ts
+++ b/src/styles/styles.ts
@@ -3456,8 +3456,6 @@ const styles = (theme: ThemeDefault) =>
taskTitleMenuItem: {
...writingDirection.ltr,
...headlineFont,
- ...flex.flexWrap,
- ...flex.flex1,
fontSize: variables.fontSizeXLarge,
maxWidth: '100%',
...wordBreak.breakWord,