diff --git a/android/app/build.gradle b/android/app/build.gradle
index ddda3199f51a..e01e62f4b6b9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001044307
- versionName "1.4.43-7"
+ versionCode 1001044311
+ versionName "1.4.43-11"
}
flavorDimensions "default"
diff --git a/docs/redirects.csv b/docs/redirects.csv
index 76b7bac3fc99..8e160e3bcdf2 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -54,3 +54,4 @@ https://help.expensify.com/articles/expensify-classic/getting-started/Employees,
https://help.expensify.com/articles/expensify-classic/getting-started/Using-The-App,https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace
https://help.expensify.com/articles/expensify-classic/getting-started/support/Expensify-Support,https://use.expensify.com/support
https://help.expensify.com/articles/expensify-classic/getting-started/Plan-Types,https://use.expensify.com/
+https://help.expensify.com/articles/new-expensify/payments/Referral-Program,https://help.expensify.com/articles/expensify-classic/get-paid-back/Referral-Program
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index f082f213a415..1a2581512eda 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.43.7
+ 1.4.43.11
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 98e9859ff5dc..7b789718fd70 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.43.7
+ 1.4.43.11
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 196d25080609..ad4e309ee295 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
1.4.43
CFBundleVersion
- 1.4.43.7
+ 1.4.43.11
NSExtension
NSExtensionPointIdentifier
diff --git a/jest.config.js b/jest.config.js
index 95ecc350ed9f..441507af4228 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -8,7 +8,7 @@ module.exports = {
`/?(*.)+(spec|test).${testFileExtension}`,
],
transform: {
- '^.+\\.jsx?$': 'babel-jest',
+ '^.+\\.[jt]sx?$': 'babel-jest',
'^.+\\.svg?$': 'jest-transformer-svg',
},
transformIgnorePatterns: ['/node_modules/(?!react-native)/'],
diff --git a/package-lock.json b/package-lock.json
index 4ddd24c2ad47..cec28a395431 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.43-7",
+ "version": "1.4.43-11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.43-7",
+ "version": "1.4.43-11",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -38,6 +38,7 @@
"@react-native-picker/picker": "2.5.1",
"@react-navigation/material-top-tabs": "^6.6.3",
"@react-navigation/native": "6.1.8",
+ "@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "6.3.16",
"@react-ng/bounds-observer": "^0.2.1",
"@rnmapbox/maps": "^10.1.11",
@@ -10258,6 +10259,17 @@
"react": "*"
}
},
+ "node_modules/@react-navigation/elements": {
+ "version": "1.3.21",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.21.tgz",
+ "integrity": "sha512-eyS2C6McNR8ihUoYfc166O1D8VYVh9KIl0UQPI8/ZJVsStlfSTgeEEh+WXge6+7SFPnZ4ewzEJdSAHH+jzcEfg==",
+ "peerDependencies": {
+ "@react-navigation/native": "^6.0.0",
+ "react": "*",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 3.0.0"
+ }
+ },
"node_modules/@react-navigation/material-top-tabs": {
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-6.6.3.tgz",
@@ -10289,6 +10301,22 @@
"react-native": "*"
}
},
+ "node_modules/@react-navigation/native-stack": {
+ "version": "6.9.17",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.9.17.tgz",
+ "integrity": "sha512-X8p8aS7JptQq7uZZNFEvfEcPf6tlK4PyVwYDdryRbG98B4bh2wFQYMThxvqa+FGEN7USEuHdv2mF0GhFKfX0ew==",
+ "dependencies": {
+ "@react-navigation/elements": "^1.3.21",
+ "warn-once": "^0.1.0"
+ },
+ "peerDependencies": {
+ "@react-navigation/native": "^6.0.0",
+ "react": "*",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 3.0.0",
+ "react-native-screens": ">= 3.0.0"
+ }
+ },
"node_modules/@react-navigation/routers": {
"version": "6.1.9",
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz",
@@ -10315,17 +10343,6 @@
"react-native-screens": ">= 3.0.0"
}
},
- "node_modules/@react-navigation/stack/node_modules/@react-navigation/elements": {
- "version": "1.3.17",
- "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.17.tgz",
- "integrity": "sha512-sui8AzHm6TxeEvWT/NEXlz3egYvCUog4tlXA4Xlb2Vxvy3purVXDq/XsM56lJl344U5Aj/jDzkVanOTMWyk4UA==",
- "peerDependencies": {
- "@react-navigation/native": "^6.0.0",
- "react": "*",
- "react-native": "*",
- "react-native-safe-area-context": ">= 3.0.0"
- }
- },
"node_modules/@react-ng/bounds-observer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@react-ng/bounds-observer/-/bounds-observer-0.2.1.tgz",
diff --git a/package.json b/package.json
index 92698102e064..379612854781 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.43-7",
+ "version": "1.4.43-11",
"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.",
@@ -86,6 +86,7 @@
"@react-native-picker/picker": "2.5.1",
"@react-navigation/material-top-tabs": "^6.6.3",
"@react-navigation/native": "6.1.8",
+ "@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "6.3.16",
"@react-ng/bounds-observer": "^0.2.1",
"@rnmapbox/maps": "^10.1.11",
diff --git a/patches/@react-navigation+native-stack+6.9.17.patch b/patches/@react-navigation+native-stack+6.9.17.patch
new file mode 100644
index 000000000000..933ca6ce792e
--- /dev/null
+++ b/patches/@react-navigation+native-stack+6.9.17.patch
@@ -0,0 +1,39 @@
+diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx
+index 206fb0b..7a34a8e 100644
+--- a/node_modules/@react-navigation/native-stack/src/types.tsx
++++ b/node_modules/@react-navigation/native-stack/src/types.tsx
+@@ -490,6 +490,14 @@ export type NativeStackNavigationOptions = {
+ * Only supported on iOS and Android.
+ */
+ freezeOnBlur?: boolean;
++ // partial changes from https://github.com/react-navigation/react-navigation/commit/90cfbf23bcc5259f3262691a9eec6c5b906e5262
++ // patch can be removed when new version of `native-stack` will be released
++ /**
++ * Whether the keyboard should hide when swiping to the previous screen. Defaults to `false`.
++ *
++ * Only supported on iOS
++ */
++ keyboardHandlingEnabled?: boolean;
+ };
+
+ export type NativeStackNavigatorProps = DefaultNavigatorOptions<
+diff --git a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx
+index a005c43..03d8b50 100644
+--- a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx
++++ b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx
+@@ -161,6 +161,7 @@ const SceneView = ({
+ statusBarTranslucent,
+ statusBarColor,
+ freezeOnBlur,
++ keyboardHandlingEnabled,
+ } = options;
+
+ let {
+@@ -289,6 +290,7 @@ const SceneView = ({
+ onNativeDismissCancelled={onNativeDismissCancelled}
+ // this prop is available since rn-screens 3.16
+ freezeOnBlur={freezeOnBlur}
++ hideKeyboardOnSwipe={keyboardHandlingEnabled}
+ >
+
+
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index c5480d363019..c41ef521661c 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -84,28 +84,28 @@ const ROUTES = {
SETTINGS_APP_DOWNLOAD_LINKS: 'settings/about/app-download-links',
SETTINGS_WALLET: 'settings/wallet',
SETTINGS_WALLET_DOMAINCARD: {
- route: '/settings/wallet/card/:domain',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}` as const,
+ route: 'settings/wallet/card/:domain',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}` as const,
},
SETTINGS_REPORT_FRAUD: {
- route: '/settings/wallet/card/:domain/report-virtual-fraud',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-virtual-fraud` as const,
+ route: 'settings/wallet/card/:domain/report-virtual-fraud',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}/report-virtual-fraud` as const,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_NAME: {
- route: '/settings/wallet/card/:domain/get-physical/name',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/name` as const,
+ route: 'settings/wallet/card/:domain/get-physical/name',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}/get-physical/name` as const,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_PHONE: {
- route: '/settings/wallet/card/:domain/get-physical/phone',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/phone` as const,
+ route: 'settings/wallet/card/:domain/get-physical/phone',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}/get-physical/phone` as const,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_ADDRESS: {
- route: '/settings/wallet/card/:domain/get-physical/address',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/address` as const,
+ route: 'settings/wallet/card/:domain/get-physical/address',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}/get-physical/address` as const,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_CONFIRM: {
- route: '/settings/wallet/card/:domain/get-physical/confirm',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/confirm` as const,
+ route: 'settings/wallet/card/:domain/get-physical/confirm',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}/get-physical/confirm` as const,
},
SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card',
SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account',
@@ -117,8 +117,8 @@ const ROUTES = {
SETTINGS_WALLET_TRANSFER_BALANCE: 'settings/wallet/transfer-balance',
SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT: 'settings/wallet/choose-transfer-account',
SETTINGS_WALLET_REPORT_CARD_LOST_OR_DAMAGED: {
- route: '/settings/wallet/card/:domain/report-card-lost-or-damaged',
- getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-card-lost-or-damaged` as const,
+ route: 'settings/wallet/card/:domain/report-card-lost-or-damaged',
+ getRoute: (domain: string) => `settings/wallet/card/${domain}/report-card-lost-or-damaged` as const,
},
SETTINGS_WALLET_CARD_ACTIVATE: {
route: 'settings/wallet/card/:domain/activate',
@@ -219,6 +219,10 @@ const ROUTES = {
route: 'r/:reportID/settings/who-can-post',
getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` as const,
},
+ REPORT_SETTINGS_VISIBILITY: {
+ route: 'r/:reportID/settings/visibility',
+ getRoute: (reportID: string) => `r/${reportID}/settings/visibility` as const,
+ },
SPLIT_BILL_DETAILS: {
route: 'r/:reportID/split/:reportActionID',
getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index ee3c64e8d804..18754a3513c1 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -167,6 +167,7 @@ const SCREENS = {
ROOM_NAME: 'Report_Settings_Room_Name',
NOTIFICATION_PREFERENCES: 'Report_Settings_Notification_Preferences',
WRITE_CAPABILITY: 'Report_Settings_Write_Capability',
+ VISIBILITY: 'Report_Settings_Visibility',
},
NEW_TASK: {
diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx
index 25dc99459064..5b5e99ac0621 100644
--- a/src/components/CountrySelector.tsx
+++ b/src/components/CountrySelector.tsx
@@ -1,4 +1,5 @@
-import React, {forwardRef, useEffect} from 'react';
+import {useIsFocused} from '@react-navigation/native';
+import React, {forwardRef, useEffect, useRef} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
@@ -23,15 +24,28 @@ type CountrySelectorProps = {
/** inputID used by the Form component */
// eslint-disable-next-line react/no-unused-prop-types
inputID: string;
+
+ /** Callback to call when the picker modal is dismissed */
+ onBlur?: () => void;
};
-function CountrySelector({errorText = '', value: countryCode, onInputChange}: CountrySelectorProps, ref: ForwardedRef) {
+function CountrySelector({errorText = '', value: countryCode, onInputChange, onBlur}: CountrySelectorProps, ref: ForwardedRef) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const title = countryCode ? translate(`allCountries.${countryCode}`) : '';
const countryTitleDescStyle = title.length === 0 ? styles.textNormal : null;
+ const didOpenContrySelector = useRef(false);
+ const isFocused = useIsFocused();
+ useEffect(() => {
+ if (!isFocused || !didOpenContrySelector.current) {
+ return;
+ }
+ didOpenContrySelector.current = false;
+ onBlur?.();
+ }, [isFocused, onBlur]);
+
useEffect(() => {
// This will cause the form to revalidate and remove any error related to country name
onInputChange(countryCode);
@@ -48,6 +62,7 @@ function CountrySelector({errorText = '', value: countryCode, onInputChange}: Co
description={translate('common.country')}
onPress={() => {
const activeRoute = Navigation.getActiveRouteWithoutParams();
+ didOpenContrySelector.current = true;
Navigation.navigate(ROUTES.SETTINGS_ADDRESS_COUNTRY.getRoute(countryCode ?? '', activeRoute));
}}
/>
diff --git a/src/components/Image/index.js b/src/components/Image/index.js
index ef1a69e19c12..59fcde8273fd 100644
--- a/src/components/Image/index.js
+++ b/src/components/Image/index.js
@@ -3,12 +3,15 @@ import React, {useEffect, useMemo} from 'react';
import {Image as RNImage} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
+import useNetwork from '@hooks/useNetwork';
import ONYXKEYS from '@src/ONYXKEYS';
import {defaultProps, imagePropTypes} from './imagePropTypes';
import RESIZE_MODES from './resizeModes';
function Image(props) {
const {source: propsSource, isAuthTokenRequired, onLoad, session} = props;
+ const {isOffline} = useNetwork();
+
/**
* Check if the image source is a URL - if so the `encryptedAuthToken` is appended
* to the source.
@@ -39,7 +42,7 @@ function Image(props) {
RNImage.getSize(source.uri, (width, height) => {
onLoad({nativeEvent: {width, height}});
});
- }, [onLoad, source]);
+ }, [onLoad, source, isOffline]);
// Omit the props which the underlying RNImage won't use
const forwardedProps = _.omit(props, ['source', 'onLoad', 'session', 'isAuthTokenRequired']);
diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx
index b3fc1dc91c16..0ca4a0456e33 100644
--- a/src/components/ImageWithSizeCalculation.tsx
+++ b/src/components/ImageWithSizeCalculation.tsx
@@ -2,6 +2,7 @@ import delay from 'lodash/delay';
import React, {useEffect, useMemo, useRef, useState} from 'react';
import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
+import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import Log from '@libs/Log';
import FullscreenLoadingIndicator from './FullscreenLoadingIndicator';
@@ -44,16 +45,27 @@ function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthT
const isLoadedRef = useRef(null);
const [isImageCached, setIsImageCached] = useState(true);
const [isLoading, setIsLoading] = useState(false);
+ const {isOffline} = useNetwork();
const source = useMemo(() => ({uri: url}), [url]);
const onError = () => {
Log.hmmm('Unable to fetch image to calculate size', {url});
onLoadFailure?.();
+ if (isLoadedRef.current) {
+ isLoadedRef.current = false;
+ setIsImageCached(false);
+ }
+ if (isOffline) {
+ return;
+ }
+ setIsLoading(false);
};
const imageLoadedSuccessfully = (event: OnLoadNativeEvent) => {
isLoadedRef.current = true;
+ setIsLoading(false);
+ setIsImageCached(true);
onMeasure({
width: event.nativeEvent.width,
height: event.nativeEvent.height,
@@ -87,10 +99,6 @@ function ImageWithSizeCalculation({url, style, onMeasure, onLoadFailure, isAuthT
}
setIsLoading(true);
}}
- onLoadEnd={() => {
- setIsLoading(false);
- setIsImageCached(true);
- }}
onError={onError}
onLoad={imageLoadedSuccessfully}
/>
diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 48e9aa49d0de..7313bb4aa7bb 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -45,7 +45,7 @@ type LocaleContextProps = {
/** Returns a locally converted phone number for numbers from the same region
* and an internationally converted phone number with the country code for numbers from other regions */
- formatPhoneNumber: (phoneNumber: string | undefined) => string;
+ formatPhoneNumber: (phoneNumber: string) => string;
/** Gets the locale digit corresponding to a standard digit */
toLocaleDigit: (digit: string) => string;
diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx
index 46c96fd706a9..584b349c508f 100644
--- a/src/components/MagicCodeInput.tsx
+++ b/src/components/MagicCodeInput.tsx
@@ -430,3 +430,4 @@ function MagicCodeInput(
MagicCodeInput.displayName = 'MagicCodeInput';
export default forwardRef(MagicCodeInput);
+export type {MagicCodeInputHandle};
diff --git a/src/components/QRShare/index.tsx b/src/components/QRShare/index.tsx
index 45a4a4fd4964..c7e9e7637a6c 100644
--- a/src/components/QRShare/index.tsx
+++ b/src/components/QRShare/index.tsx
@@ -9,15 +9,12 @@ import QRCode from '@components/QRCode';
import Text from '@components/Text';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import useWindowDimensions from '@hooks/useWindowDimensions';
import variables from '@styles/variables';
-import CONST from '@src/CONST';
import type {QRShareHandle, QRShareProps} from './types';
function QRShare({url, title, subtitle, logo, logoRatio, logoMarginRatio}: QRShareProps, ref: ForwardedRef) {
const styles = useThemeStyles();
const theme = useTheme();
- const {isSmallScreenWidth} = useWindowDimensions();
const [qrCodeSize, setQrCodeSize] = useState(1);
const svgRef = useRef