diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 755ab6dbaa60..6ded44d7059f 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -354,6 +354,6 @@ jobs: IOS: ${{ needs.iOS.result }} WEB: ${{ needs.web.result }} ANDROID_LINK: ${{steps.get_android_path.outputs.android_path}} - DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ env.PULL_REQUEST_NUMBER }}/NewExpensifyAdHoc.dmg + DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ env.PULL_REQUEST_NUMBER }}/NewExpensify.dmg IOS_LINK: ${{steps.get_ios_path.outputs.ios_path}} WEB_LINK: https://${{ env.PULL_REQUEST_NUMBER }}.pr-testing.expensify.com diff --git a/android/app/build.gradle b/android/app/build.gradle index 1867a8cf85d2..c6a9c3147118 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 1001037002 - versionName "1.3.70-2" + versionCode 1001037007 + versionName "1.3.70-7" } flavorDimensions "default" diff --git a/config/electronBuilder.config.js b/config/electronBuilder.config.js index a5478dbd8f78..da87c93ee367 100644 --- a/config/electronBuilder.config.js +++ b/config/electronBuilder.config.js @@ -21,24 +21,6 @@ const macIcon = { adhoc: './desktop/icon-adhoc.png', }; -const appIds = { - production: 'com.expensifyreactnative.chat', - staging: 'com.expensifyreactnative.dev.chat', - adhoc: 'com.expensifyreactnative.adhoc.chat', -}; - -const productNames = { - production: 'New Expensify', - staging: 'New Expensify Dev', - adhoc: 'New Expensify AdHoc', -}; - -const artifactNames = { - production: 'NewExpensify.dmg', - staging: 'NewExpensifyDev.dmg', - adhoc: 'NewExpensifyAdHoc.dmg', -}; - const isCorrectElectronEnv = ['production', 'staging', 'adhoc'].includes(process.env.ELECTRON_ENV); if (!isCorrectElectronEnv) { @@ -50,8 +32,8 @@ if (!isCorrectElectronEnv) { * It can be used to create local builds of the same, by omitting the `--publish` flag */ module.exports = { - appId: appIds[process.env.ELECTRON_ENV], - productName: productNames[process.env.ELECTRON_ENV], + appId: 'com.expensifyreactnative.chat', + productName: 'New Expensify', extraMetadata: { version, }, @@ -64,8 +46,8 @@ module.exports = { type: 'distribution', }, dmg: { - title: productNames[process.env.ELECTRON_ENV], - artifactName: artifactNames[process.env.ELECTRON_ENV], + title: 'New Expensify', + artifactName: 'NewExpensify.dmg', internetEnabled: true, }, publish: [ @@ -83,7 +65,7 @@ module.exports = { output: 'desktop-build', }, protocols: { - name: productNames[process.env.ELECTRON_ENV], + name: 'New Expensify', schemes: ['new-expensify'], }, }; diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index 01ebb00b288c..f0f335536c20 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -206,13 +206,6 @@ window.addEventListener('DOMContentLoaded', () => { // If there is a fixed article scroll container, set to calculate titles' offset scrollContainer: 'content-area', - - // onclick function to apply to all links in toc. will be called with - // the event as the first parameter, and this can be used to stop, - // propagation, prevent default or perform action - onClick() { - toggleHeaderMenu(); - }, }); } @@ -226,6 +219,18 @@ window.addEventListener('DOMContentLoaded', () => { const articleContent = document.getElementById('article-content'); const lhnContent = document.getElementById('lhn-content'); + + // This event listener checks if a link clicked in the LHN points to some section of the same page and toggles + // the LHN menu in responsive view. + lhnContent.addEventListener('click', (event) => { + const clickedLink = event.target; + if (clickedLink) { + const href = clickedLink.getAttribute('href'); + if (href && href.startsWith('#') && !!document.getElementById(href.slice(1))) { + toggleHeaderMenu(); + } + } + }); lhnContent.addEventListener('wheel', (e) => { const scrollTop = lhnContent.scrollTop; const isScrollingPastLHNTop = e.deltaY < 0 && scrollTop === 0; diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 9e4501eddea5..03dcc7770df0 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.70.2 + 1.3.70.7 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index fd93684a1da3..941d232244e1 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.70.2 + 1.3.70.7 diff --git a/package-lock.json b/package-lock.json index 0ba372b22745..382dcf45f55e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.70-2", + "version": "1.3.70-7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.70-2", + "version": "1.3.70-7", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -52,7 +52,6 @@ "domhandler": "^4.3.0", "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#35bff866a8d345b460ea6256f0a0f0a8a7f81086", "fbjs": "^3.0.2", - "focus-trap-react": "^10.2.1", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", "jest-when": "^3.5.2", @@ -90,10 +89,10 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.76", + "react-native-onyx": "1.0.77", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", - "react-native-performance": "^4.0.0", + "react-native-performance": "^5.1.0", "react-native-permissions": "^3.0.1", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", "react-native-plaid-link-sdk": "^10.0.0", @@ -103,7 +102,7 @@ "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.21.0", - "react-native-svg": "^13.9.0", + "react-native-svg": "^13.13.0", "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", @@ -28295,28 +28294,6 @@ "readable-stream": "^2.3.6" } }, - "node_modules/focus-trap": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", - "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/focus-trap-react": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz", - "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==", - "dependencies": { - "focus-trap": "^7.5.2", - "tabbable": "^6.2.0" - }, - "peerDependencies": { - "prop-types": "^15.8.1", - "react": ">=16.3.0", - "react-dom": ">=16.3.0" - } - }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -40567,9 +40544,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.76", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.76.tgz", - "integrity": "sha512-mcMlYQCo1B/kom+4hu7CQKKLwvPFjQAJsVIzV2s9aa8XKNlcnYiJbfuM6RSJ1fFmSIeud4Y66rhv4/oWUkSl5A==", + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.77.tgz", + "integrity": "sha512-HmeS1Pz/BkKNbYuhWULC9I0VRBDt8yadG0ZFIW6wuZ+VajhjD960qh7Il1+XzEBI6Vb4d7BZkPcad87ad1IEOQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -40583,7 +40560,7 @@ "idb-keyval": "^6.2.1", "react": ">=18.1.0", "react-native-device-info": "^10.3.0", - "react-native-performance": "^4.0.0", + "react-native-performance": "^5.1.0", "react-native-quick-sqlite": "^8.0.0-beta.2" }, "peerDependenciesMeta": { @@ -40625,8 +40602,9 @@ } }, "node_modules/react-native-performance": { - "version": "4.0.0", - "license": "MIT", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-native-performance/-/react-native-performance-5.1.0.tgz", + "integrity": "sha512-rq/YBf0/GptSOM/Lj64/1yRq8uN2YE0psFB16wFbYBbTcIEp/0rrgN2HyS5lhvfBOFgKoDRWQ53jHSCb+QJ5eA==", "peerDependencies": { "react-native": "*" } @@ -40783,8 +40761,9 @@ } }, "node_modules/react-native-svg": { - "version": "13.9.0", - "license": "MIT", + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.13.0.tgz", + "integrity": "sha512-L8y8uEiMG0Tr++Nb2+24wlMuv18+bmq/CMoFFtTUlEqVvGCoK2ea8WamPl/9bV8gjL+Rngg5NqEBvKS23sbYoA==", "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3" @@ -44692,11 +44671,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" - }, "node_modules/table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -67708,23 +67682,6 @@ "readable-stream": "^2.3.6" } }, - "focus-trap": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", - "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", - "requires": { - "tabbable": "^6.2.0" - } - }, - "focus-trap-react": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz", - "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==", - "requires": { - "focus-trap": "^7.5.2", - "tabbable": "^6.2.0" - } - }, "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -76116,9 +76073,9 @@ } }, "react-native-onyx": { - "version": "1.0.76", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.76.tgz", - "integrity": "sha512-mcMlYQCo1B/kom+4hu7CQKKLwvPFjQAJsVIzV2s9aa8XKNlcnYiJbfuM6RSJ1fFmSIeud4Y66rhv4/oWUkSl5A==", + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.77.tgz", + "integrity": "sha512-HmeS1Pz/BkKNbYuhWULC9I0VRBDt8yadG0ZFIW6wuZ+VajhjD960qh7Il1+XzEBI6Vb4d7BZkPcad87ad1IEOQ==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -76141,7 +76098,9 @@ } }, "react-native-performance": { - "version": "4.0.0", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-native-performance/-/react-native-performance-5.1.0.tgz", + "integrity": "sha512-rq/YBf0/GptSOM/Lj64/1yRq8uN2YE0psFB16wFbYBbTcIEp/0rrgN2HyS5lhvfBOFgKoDRWQ53jHSCb+QJ5eA==", "requires": {} }, "react-native-performance-flipper-reporter": { @@ -76238,7 +76197,9 @@ } }, "react-native-svg": { - "version": "13.9.0", + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.13.0.tgz", + "integrity": "sha512-L8y8uEiMG0Tr++Nb2+24wlMuv18+bmq/CMoFFtTUlEqVvGCoK2ea8WamPl/9bV8gjL+Rngg5NqEBvKS23sbYoA==", "requires": { "css-select": "^5.1.0", "css-tree": "^1.1.3" @@ -78848,11 +78809,6 @@ "version": "2.0.15", "dev": true }, - "tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" - }, "table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", diff --git a/package.json b/package.json index 97621503eb6f..0073dedb741c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.70-2", + "version": "1.3.70-7", "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.", @@ -94,7 +94,6 @@ "domhandler": "^4.3.0", "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#35bff866a8d345b460ea6256f0a0f0a8a7f81086", "fbjs": "^3.0.2", - "focus-trap-react": "^10.2.1", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", "jest-when": "^3.5.2", @@ -132,10 +131,10 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.76", + "react-native-onyx": "1.0.77", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", - "react-native-performance": "^4.0.0", + "react-native-performance": "^5.1.0", "react-native-permissions": "^3.0.1", "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", "react-native-plaid-link-sdk": "^10.0.0", @@ -145,7 +144,7 @@ "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.21.0", - "react-native-svg": "^13.9.0", + "react-native-svg": "^13.13.0", "react-native-tab-view": "^3.5.2", "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", diff --git a/src/CONST.ts b/src/CONST.ts index 1ef2f3e83246..762186439cec 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -293,8 +293,8 @@ const CONST = { }, type: KEYBOARD_SHORTCUT_NAVIGATION_TYPE, }, - NEW_GROUP: { - descriptionKey: 'newGroup', + NEW_CHAT: { + descriptionKey: 'newChat', shortcutKey: 'K', modifiers: ['CTRL', 'SHIFT'], trigger: { @@ -1170,6 +1170,7 @@ const CONST = { SMALL_NORMAL: 'small-normal', }, EXPENSIFY_CARD: { + BANK: 'Expensify Card', FRAUD_TYPES: { DOMAIN: 'domain', INDIVIDUAL: 'individal', @@ -1342,6 +1343,7 @@ const CONST = { SETTINGS: 'settings', LEAVE_ROOM: 'leaveRoom', WELCOME_MESSAGE: 'welcomeMessage', + PRIVATE_NOTES: 'privateNotes', }, EDIT_REQUEST_FIELD: { AMOUNT: 'amount', @@ -2624,6 +2626,9 @@ const CONST = { DISABLED: 'DISABLED', }, TAB: { + NEW_CHAT_TAB_ID: 'NewChatTab', + NEW_CHAT: 'chat', + NEW_ROOM: 'room', RECEIPT_TAB_ID: 'ReceiptTab', MANUAL: 'manual', SCAN: 'scan', diff --git a/src/Expensify.js b/src/Expensify.js index 1086bd32cff9..fba65e42c06c 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -30,7 +30,6 @@ import KeyboardShortcutsModal from './components/KeyboardShortcutsModal'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; -import DownloadAppModal from './components/DownloadAppModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection @@ -193,7 +192,6 @@ function Expensify(props) { {shouldInit && ( <> - diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index eb649053c93f..f16f8129e86c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -87,9 +87,6 @@ const ONYXKEYS = { SESSION: 'session', BETAS: 'betas', - /** Denotes if the Download App Banner has been dismissed */ - SHOW_DOWNLOAD_APP_BANNER: 'showDownloadAppBanner', - /** NVP keys * Contains the user's payPalMe data */ PAYPAL: 'paypal', @@ -291,6 +288,7 @@ const ONYXKEYS = { SETTINGS_STATUS_SET_FORM: 'settingsStatusSetForm', SETTINGS_STATUS_CLEAR_AFTER_FORM: 'settingsStatusClearAfterForm', SETTINGS_STATUS_SET_CLEAR_AFTER_FORM: 'settingsStatusSetClearAfterForm', + PRIVATE_NOTES_FORM: 'privateNotesForm', I_KNOW_A_TEACHER_FORM: 'iKnowTeacherForm', INTRO_SCHOOL_PRINCIPAL_FORM: 'introSchoolPrincipalForm', }, @@ -307,7 +305,6 @@ type OnyxValues = { [ONYXKEYS.ACTIVE_CLIENTS]: string[]; [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; - [ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER]: boolean; [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; [ONYXKEYS.QUEUED_ONYX_UPDATES]: OnyxTypes.QueuedOnyxUpdates; [ONYXKEYS.CURRENT_DATE]: string; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1133dcec8e9a..9459708c893b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -13,7 +13,6 @@ type ParseReportRouteParams = { const REPORT = 'r'; const IOU_REQUEST = 'request/new'; -const IOU_BILL = 'split/new'; const IOU_SEND = 'send/new'; const NEW_TASK = 'new/task'; const SETTINGS_PERSONAL_DETAILS = 'settings/profile/personal-details'; @@ -67,8 +66,9 @@ export default { SETTINGS_2FA: 'settings/security/two-factor-auth', SETTINGS_STATUS, SETTINGS_STATUS_SET, - NEW_GROUP: 'new/group', + NEW: 'new', NEW_CHAT: 'new/chat', + NEW_ROOM: 'new/room', NEW_TASK, REPORT, REPORT_WITH_ID: 'r/:reportID?/:reportActionID?', @@ -86,7 +86,6 @@ export default { CONCIERGE: 'concierge', IOU_REQUEST, - IOU_BILL, IOU_SEND, // To see the available iouType, please refer to CONST.IOU.MONEY_REQUEST_TYPE @@ -174,6 +173,14 @@ export default { GOOGLE_SIGN_IN: 'sign-in-with-google', DESKTOP_SIGN_IN_REDIRECT: 'desktop-signin-redirect', + // Routes related to private notes added to the report + PRIVATE_NOTES_VIEW: 'r/:reportID/notes/:accountID', + getPrivateNotesViewRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}`, + PRIVATE_NOTES_LIST: 'r/:reportID/notes', + getPrivateNotesListRoute: (reportID: string) => `r/${reportID}/notes`, + PRIVATE_NOTES_EDIT: 'r/:reportID/notes/:accountID/edit', + getPrivateNotesEditRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit`, + // This is a special validation URL that will take the user to /workspace/new after validation. This is used // when linking users from e.com in order to share a session in this app. ENABLE_PAYMENTS: 'enable-payments', diff --git a/src/components/AnimatedStep/index.js b/src/components/AnimatedStep/index.js index a8b9b80fcc0e..5b0dc8bc78fa 100644 --- a/src/components/AnimatedStep/index.js +++ b/src/components/AnimatedStep/index.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import * as Animatable from 'react-native-animatable'; import CONST from '../../CONST'; import styles from '../../styles/styles'; +import useNativeDriver from '../../libs/useNativeDriver'; const propTypes = { /** Children to wrap in AnimatedStep. */ @@ -47,7 +48,7 @@ function AnimatedStep(props) { }} duration={CONST.ANIMATED_TRANSITION} animation={getAnimationStyle(props.direction)} - useNativeDriver + useNativeDriver={useNativeDriver} style={props.style} > {props.children} diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index d39906faf3a3..bbb0662132d2 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -25,6 +25,7 @@ import HeaderGap from './HeaderGap'; import SafeAreaConsumer from './SafeAreaConsumer'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import reportPropTypes from '../pages/reportPropTypes'; +import useNativeDriver from '../libs/useNativeDriver'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -294,7 +295,7 @@ function AttachmentModal(props) { Animated.timing(confirmButtonFadeAnimation, { toValue, duration: 100, - useNativeDriver: true, + useNativeDriver, }).start(); }, [confirmButtonFadeAnimation], diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index d5da25c89576..8a623a44709f 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -59,6 +59,7 @@ function extractAttachmentsFromReport(report, reportActions) { isAuthTokenRequired: true, file: {name: transaction.filename}, isReceipt: true, + transactionID, }); return; } diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 5c731a0ccfee..574cb496d02f 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -19,6 +19,7 @@ import BlockingView from '../../BlockingViews/BlockingView'; import * as Illustrations from '../../Icon/Illustrations'; import variables from '../../../styles/variables'; import * as DeviceCapabilities from '../../../libs/DeviceCapabilities'; +import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; const viewabilityConfig = { // To facilitate paging through the attachments, we want to consider an item "viewable" when it is @@ -38,13 +39,25 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl const [activeSource, setActiveSource] = useState(source); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const compareImage = useCallback( + (attachment) => { + if (attachment.isReceipt) { + const action = ReportActionsUtils.getParentReportAction(report); + const transactionID = _.get(action, ['originalMessage', 'IOUTransactionID']); + return attachment.transactionID === transactionID; + } + return attachment.source === source; + }, + [source, report], + ); + useEffect(() => { const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions); - const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source); + const initialPage = _.findIndex(attachmentsFromReport, compareImage); // Dismiss the modal when deleting an attachment during its display in preview. - if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) { + if (initialPage === -1 && _.find(attachments, compareImage)) { Navigation.dismissModal(); } else { setPage(initialPage); @@ -57,7 +70,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [report, reportActions, source]); + }, [reportActions, compareImage]); /** * Updates the page state when the user navigates between attachments diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index 95cda7c2f5c9..a7a2f35a2ccc 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -16,6 +16,7 @@ import * as Illustrations from '../../Icon/Illustrations'; import variables from '../../../styles/variables'; import compose from '../../../libs/compose'; import withLocalize from '../../withLocalize'; +import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, setDownloadButtonVisibility, translate}) { const pagerRef = useRef(null); @@ -27,13 +28,25 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const compareImage = useCallback( + (attachment) => { + if (attachment.isReceipt) { + const action = ReportActionsUtils.getParentReportAction(report); + const transactionID = _.get(action, ['originalMessage', 'IOUTransactionID']); + return attachment.transactionID === transactionID; + } + return attachment.source === source; + }, + [source, report], + ); + useEffect(() => { const attachmentsFromReport = extractAttachmentsFromReport(report, reportActions); - const initialPage = _.findIndex(attachmentsFromReport, (a) => a.source === source); + const initialPage = _.findIndex(attachmentsFromReport, compareImage); // Dismiss the modal when deleting an attachment during its display in preview. - if (initialPage === -1 && _.find(attachments, (a) => a.source === source)) { + if (initialPage === -1 && _.find(attachments, compareImage)) { Navigation.dismissModal(); } else { setPage(initialPage); @@ -46,7 +59,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, if (!_.isUndefined(attachmentsFromReport[initialPage])) onNavigate(attachmentsFromReport[initialPage]); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [report, reportActions, source]); + }, [reportActions, compareImage]); /** * Updates the page state when the user navigates between attachments diff --git a/src/components/Button/index.js b/src/components/Button/index.js index bfde528a4750..c16860344837 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -218,6 +218,7 @@ class Button extends Component { this.props.icon && styles.textAlignLeft, ...this.props.textStyles, ]} + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > {this.props.text} diff --git a/src/components/CollapsibleSection/index.js b/src/components/CollapsibleSection/index.js index e9c3a90a7b30..7009d1905e1d 100644 --- a/src/components/CollapsibleSection/index.js +++ b/src/components/CollapsibleSection/index.js @@ -51,6 +51,7 @@ class CollapsibleSection extends React.Component { {this.props.title} diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js index 9a72d4e7d584..ab3e23d6b1c1 100644 --- a/src/components/ConfirmContent.js +++ b/src/components/ConfirmContent.js @@ -100,8 +100,8 @@ function ConfirmContent(props) { diff --git a/src/components/DownloadAppModal.js b/src/components/DownloadAppModal.js deleted file mode 100644 index c96c6b3d28c0..000000000000 --- a/src/components/DownloadAppModal.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, {useState} from 'react'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../ONYXKEYS'; -import styles from '../styles/styles'; -import CONST from '../CONST'; -import AppIcon from '../../assets/images/expensify-app-icon.svg'; -import useLocalize from '../hooks/useLocalize'; -import * as Link from '../libs/actions/Link'; -import * as Browser from '../libs/Browser'; -import getOperatingSystem from '../libs/getOperatingSystem'; -import setShowDownloadAppModal from '../libs/actions/DownloadAppModal'; -import ConfirmModal from './ConfirmModal'; - -const propTypes = { - /** ONYX PROP to hide banner for a user that has dismissed it */ - // eslint-disable-next-line react/forbid-prop-types - showDownloadAppBanner: PropTypes.bool, - - /** Whether the user is logged in */ - isAuthenticated: PropTypes.bool.isRequired, -}; - -const defaultProps = { - showDownloadAppBanner: true, -}; - -function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) { - const [shouldShowBanner, setShouldShowBanner] = useState(Browser.isMobile() && isAuthenticated && showDownloadAppBanner); - - const {translate} = useLocalize(); - - const handleCloseBanner = () => { - setShowDownloadAppModal(false); - setShouldShowBanner(false); - }; - - let link = ''; - - if (getOperatingSystem() === CONST.OS.IOS) { - link = CONST.APP_DOWNLOAD_LINKS.IOS; - } else if (getOperatingSystem() === CONST.OS.ANDROID) { - link = CONST.APP_DOWNLOAD_LINKS.ANDROID; - } - - const handleOpenAppStore = () => { - setShowDownloadAppModal(false); - setShouldShowBanner(false); - Link.openExternalLink(link, true); - }; - - return ( - - ); -} - -DownloadAppModal.displayName = 'DownloadAppModal'; -DownloadAppModal.propTypes = propTypes; -DownloadAppModal.defaultProps = defaultProps; - -export default withOnyx({ - showDownloadAppBanner: { - key: ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER, - }, -})(DownloadAppModal); diff --git a/src/components/EmojiPicker/EmojiPicker.js b/src/components/EmojiPicker/EmojiPicker.js index 40d91ff03267..a12b089ddf97 100644 --- a/src/components/EmojiPicker/EmojiPicker.js +++ b/src/components/EmojiPicker/EmojiPicker.js @@ -114,9 +114,11 @@ const EmojiPicker = forwardRef((props, ref) => { */ const isActive = (id) => Boolean(id) && id === activeID; + const clearActive = () => setActiveID(null); + const resetEmojiPopoverAnchor = () => (emojiPopoverAnchor.current = null); - useImperativeHandle(ref, () => ({showEmojiPicker, isActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor})); + useImperativeHandle(ref, () => ({showEmojiPicker, isActive, clearActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor})); useEffect(() => { const emojiPopoverDimensionListener = Dimensions.addEventListener('change', () => { diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 728e56792ddb..b51a8b07537c 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -72,15 +72,16 @@ class EmojiPickerMenuItem extends PureComponent { this.props.onPress(this.props.emoji)} + onPressOut={Browser.isMobile() ? this.props.onHoverOut : undefined} onHoverIn={this.props.onHoverIn} onHoverOut={this.props.onHoverOut} onFocus={this.props.onFocus} onBlur={this.props.onBlur} ref={(ref) => (this.ref = ref)} style={({pressed}) => [ - Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), this.props.isHighlighted && this.props.isUsingKeyboardMovement ? styles.emojiItemKeyboardHighlighted : {}, this.props.isHighlighted && !this.props.isUsingKeyboardMovement ? styles.emojiItemHighlighted : {}, + Browser.isMobile() && StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.emojiItem, ]} accessibilityLabel={this.props.emoji} diff --git a/src/components/FocusTrapView/index.js b/src/components/FocusTrapView/index.js deleted file mode 100644 index 2dcab7b9d998..000000000000 --- a/src/components/FocusTrapView/index.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * The FocusTrap is only used on web and desktop - */ -import React, {useEffect, useRef} from 'react'; -import FocusTrap from 'focus-trap-react'; -import {View} from 'react-native'; -import {PropTypes} from 'prop-types'; -import {useIsFocused} from '@react-navigation/native'; - -const propTypes = { - /** Children to wrap with FocusTrap */ - children: PropTypes.node.isRequired, - - /** Whether to enable the FocusTrap */ - enabled: PropTypes.bool, - - /** - * Whether to disable auto focus - * It is used when the component inside the FocusTrap have their own auto focus logic - */ - shouldEnableAutoFocus: PropTypes.bool, -}; - -const defaultProps = { - enabled: true, - shouldEnableAutoFocus: false, -}; - -function FocusTrapView({enabled, shouldEnableAutoFocus, ...props}) { - const isFocused = useIsFocused(); - - /** - * Focus trap always needs a focusable element. - * In case that we don't have any focusable elements in the modal, - * the FocusTrap will use fallback View element using this ref. - */ - const ref = useRef(null); - - /** - * We have to set the 'tabindex' attribute to 0 to make the View focusable. - * Currently, it is not possible to set this through props. - * After the upgrade of 'react-native-web' to version 0.19 we can use 'tabIndex={0}' prop instead. - */ - useEffect(() => { - if (!ref.current) { - return; - } - ref.current.setAttribute('tabindex', '0'); - }, []); - - return enabled ? ( - shouldEnableAutoFocus && ref.current, - fallbackFocus: () => ref.current, - clickOutsideDeactivates: true, - }} - > - - - ) : ( - props.children - ); -} - -FocusTrapView.displayName = 'FocusTrapView'; -FocusTrapView.propTypes = propTypes; -FocusTrapView.defaultProps = defaultProps; - -export default FocusTrapView; diff --git a/src/components/FocusTrapView/index.native.js b/src/components/FocusTrapView/index.native.js deleted file mode 100644 index 5720601f5a2b..000000000000 --- a/src/components/FocusTrapView/index.native.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * The FocusTrap is only used on web and desktop - */ - -function FocusTrapView({children}) { - return children; -} - -FocusTrapView.displayName = 'FocusTrapView'; - -export default FocusTrapView; diff --git a/src/components/GrowlNotification/index.js b/src/components/GrowlNotification/index.js index 70cadd5efd8e..a06185ac3320 100644 --- a/src/components/GrowlNotification/index.js +++ b/src/components/GrowlNotification/index.js @@ -10,6 +10,7 @@ import GrowlNotificationContainer from './GrowlNotificationContainer'; import CONST from '../../CONST'; import * as Growl from '../../libs/Growl'; import * as Pressables from '../Pressable'; +import useNativeDriver from '../../libs/useNativeDriver'; const types = { [CONST.GROWL.SUCCESS]: { @@ -59,7 +60,7 @@ function GrowlNotification(_, ref) { Animated.spring(translateY, { toValue: val, duration: 80, - useNativeDriver: true, + useNativeDriver, }).start(); }, [translateY], diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js index d91510c3ec6a..262a4d1f178e 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import React from 'react'; +import CONST from '../../../CONST'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; import Text from '../../Text'; @@ -28,6 +29,7 @@ function EditedRenderer(props) { {' '} diff --git a/src/components/IllustratedHeaderPageLayout.js b/src/components/IllustratedHeaderPageLayout.js index be4cb12d935e..92a9c8b8552b 100644 --- a/src/components/IllustratedHeaderPageLayout.js +++ b/src/components/IllustratedHeaderPageLayout.js @@ -12,6 +12,7 @@ import * as StyleUtils from '../styles/StyleUtils'; import useWindowDimensions from '../hooks/useWindowDimensions'; import FixedFooter from './FixedFooter'; import useNetwork from '../hooks/useNetwork'; +import * as Browser from '../libs/Browser'; const propTypes = { ...headerWithBackButtonPropTypes, @@ -39,14 +40,16 @@ const defaultProps = { }; function IllustratedHeaderPageLayout({backgroundColor, children, illustration, footer, overlayContent, ...propsToPassToHeader}) { - const {windowHeight} = useWindowDimensions(); const {isOffline} = useNetwork(); + const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); + const appBGColor = StyleUtils.getBackgroundColorStyle(themeColors.appBG); + return ( {({safeAreaPaddingBottomStyle}) => ( <> @@ -56,12 +59,19 @@ function IllustratedHeaderPageLayout({backgroundColor, children, illustration, f titleColor={backgroundColor === themeColors.appBG ? undefined : themeColors.textColorfulBackground} iconFill={backgroundColor === themeColors.appBG ? undefined : themeColors.iconColorfulBackground} /> - + + {/* Safari on ios/mac has a bug where overscrolling the page scrollview shows green the background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */} + {Browser.isSafari() && ( + + + + + )} - + {!Browser.isSafari() && } {overlayContent && overlayContent()} - {children} + {children} {!_.isNull(footer) && {footer}} diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b21a275a6597..454aacc8a03b 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -258,7 +258,13 @@ function MagicCodeInput(props) { {/* Hide the input above the text. Cannot set opacity to 0 as it would break pasting on iOS Safari. */} (inputRefs.current[index] = ref)} + ref={(ref) => { + inputRefs.current[index] = ref; + // Setting attribute type to "search" to prevent Password Manager from appearing in Mobile Chrome + if (ref && ref.setAttribute) { + ref.setAttribute('type', 'search'); + } + }} autoFocus={index === 0 && props.autoFocus} inputMode="numeric" textContentType="oneTimeCode" diff --git a/src/components/MapView/responder/index.android.ts b/src/components/MapView/responder/index.android.ts new file mode 100644 index 000000000000..a0fce71d8ef5 --- /dev/null +++ b/src/components/MapView/responder/index.android.ts @@ -0,0 +1,8 @@ +import {PanResponder} from 'react-native'; + +const responder = PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onPanResponderTerminationRequest: () => false, +}); + +export default responder; diff --git a/src/components/MapView/responder/index.ts b/src/components/MapView/responder/index.ts index a0fce71d8ef5..422d6c1b4963 100644 --- a/src/components/MapView/responder/index.ts +++ b/src/components/MapView/responder/index.ts @@ -1,7 +1,7 @@ import {PanResponder} from 'react-native'; const responder = PanResponder.create({ - onStartShouldSetPanResponder: () => true, + onMoveShouldSetPanResponder: () => true, onPanResponderTerminationRequest: () => false, }); diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index c39c1d503258..88d3df3b7001 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -23,6 +23,7 @@ import variables from '../styles/variables'; import * as Session from '../libs/actions/Session'; import Hoverable from './Hoverable'; import useWindowDimensions from '../hooks/useWindowDimensions'; +import RenderHTML from './RenderHTML'; const propTypes = menuItemPropTypes; @@ -73,6 +74,7 @@ const defaultProps = { title: '', numberOfLinesTitle: 1, shouldGreyOutWhenDisabled: true, + shouldRenderAsHTML: false, }; const MenuItem = React.forwardRef((props, ref) => { @@ -220,10 +222,13 @@ const MenuItem = React.forwardRef((props, ref) => { )} - {Boolean(props.title) && ( + {Boolean(props.title) && Boolean(props.shouldRenderAsHTML) && } + + {Boolean(props.title) && !props.shouldRenderAsHTML && ( {convertToLTR(props.title)} diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index ab9d420f949c..fc64d8f38243 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -13,6 +13,7 @@ import useWindowDimensions from '../../hooks/useWindowDimensions'; import variables from '../../styles/variables'; import CONST from '../../CONST'; import ComposerFocusManager from '../../libs/ComposerFocusManager'; +import useNativeDriver from '../../libs/useNativeDriver'; const propTypes = { ...modalPropTypes, @@ -40,7 +41,7 @@ function BaseModal({ fullscreen, animationIn, animationOut, - useNativeDriver, + useNativeDriver: useNativeDriverProp, hideModalContentWhileAnimating, animationInTiming, animationOutTiming, @@ -187,7 +188,7 @@ function BaseModal({ deviceWidth={windowWidth} animationIn={animationIn || modalStyleAnimationIn} animationOut={animationOut || modalStyleAnimationOut} - useNativeDriver={useNativeDriver} + useNativeDriver={useNativeDriverProp && useNativeDriver} hideModalContentWhileAnimating={hideModalContentWhileAnimating} animationInTiming={animationInTiming} animationOutTiming={animationOutTiming} diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index 4c6ba1307fb7..916646b5619a 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -221,6 +221,7 @@ function MultipleAvatars(props) { {`+${avatars.length - props.maxAvatarsInRow}`} @@ -278,6 +279,7 @@ function MultipleAvatars(props) { {`+${props.icons.length - 1}`} diff --git a/src/components/NewDatePicker/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js index fe0c36d32e41..1e1ef3c3fad3 100644 --- a/src/components/NewDatePicker/CalendarPicker/index.js +++ b/src/components/NewDatePicker/CalendarPicker/index.js @@ -130,7 +130,10 @@ class CalendarPicker extends React.PureComponent { return ( - + this.setState({isYearPickerVisible: true})} style={[styles.alignItemsCenter, styles.flexRow, styles.flex1, styles.justifyContentStart]} @@ -186,6 +189,7 @@ class CalendarPicker extends React.PureComponent { {dayOfWeek[0]} @@ -212,6 +216,7 @@ class CalendarPicker extends React.PureComponent { accessibilityLabel={day ? day.toString() : undefined} focusable={Boolean(day)} accessible={Boolean(day)} + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > {({hovered, pressed}) => ( {}, highlightSelected: false, isSelected: false, boldStyle: false, @@ -100,6 +113,7 @@ class OptionRow extends Component { this.props.isMultilineSupported !== nextProps.isMultilineSupported || this.props.isSelected !== nextProps.isSelected || this.props.shouldHaveOptionSeparator !== nextProps.shouldHaveOptionSeparator || + this.props.selectedStateButtonText !== nextProps.selectedStateButtonText || this.props.showSelectedState !== nextProps.showSelectedState || this.props.highlightSelected !== nextProps.highlightSelected || this.props.showTitleTooltip !== nextProps.showTitleTooltip || @@ -259,7 +273,26 @@ class OptionRow extends Component { /> )} - {this.props.showSelectedState && } + {this.props.showSelectedState && ( + <> + {this.props.shouldShowSelectedStateAsButton && !this.props.isSelected ? ( +