From 2b5d385d340833ebd90908744d295229960e705d Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:17:41 -0800 Subject: [PATCH 001/108] Install react-native-device-info --- ios/Podfile.lock | 6 ++++++ package-lock.json | 15 +++++++++++++++ package.json | 1 + 3 files changed, 22 insertions(+) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0637e1334ff3..dc45b4b7d650 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -575,6 +575,8 @@ PODS: - React-Core - RNDateTimePicker (3.5.2): - React-Core + - RNDeviceInfo (10.3.0): + - React-Core - RNFastImage (8.6.3): - React-Core - SDWebImage (~> 5.11.1) @@ -730,6 +732,7 @@ DEPENDENCIES: - "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)" - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" + - RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNFastImage (from `../node_modules/react-native-fast-image`) - "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)" - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" @@ -901,6 +904,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-picker/picker" RNDateTimePicker: :path: "../node_modules/@react-native-community/datetimepicker" + RNDeviceInfo: + :path: "../node_modules/react-native-device-info" RNFastImage: :path: "../node_modules/react-native-fast-image" RNFBAnalytics: @@ -1019,6 +1024,7 @@ SPEC CHECKSUMS: RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495 RNCPicker: 0b65be85fe7954fbb2062ef079e3d1cde252d888 RNDateTimePicker: 7658208086d86d09e1627b5c34ba0cf237c60140 + RNDeviceInfo: 4701f0bf2a06b34654745053db0ce4cb0c53ada7 RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 RNFBAnalytics: f76bfa164ac235b00505deb9fc1776634056898c RNFBApp: 729c0666395b1953198dc4a1ec6deb8fbe1c302e diff --git a/package-lock.json b/package-lock.json index e095176baa42..dd6b9c5b764e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "react-native-blob-util": "^0.16.2", "react-native-collapsible": "^1.6.0", "react-native-config": "^1.4.5", + "react-native-device-info": "^10.3.0", "react-native-document-picker": "^8.0.0", "react-native-fast-image": "^8.6.3", "react-native-gesture-handler": "2.6.0", @@ -35498,6 +35499,14 @@ } } }, + "node_modules/react-native-device-info": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.3.0.tgz", + "integrity": "sha512-/ziZN1sA1REbJTv5mQZ4tXggcTvSbct+u5kCaze8BmN//lbxcTvWsU6NQd4IihLt89VkbX+14IGc9sVApSxd/w==", + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-document-picker": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-8.1.1.tgz", @@ -69955,6 +69964,12 @@ "integrity": "sha512-cSLdOfva2IPCxh6HjHN1IDVW9ratAvNnnAUx6ar2Byvr8KQU7++ysdFYPaoNVuJURuYoAKgvjab8ZcnwGZIO6Q==", "requires": {} }, + "react-native-device-info": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.3.0.tgz", + "integrity": "sha512-/ziZN1sA1REbJTv5mQZ4tXggcTvSbct+u5kCaze8BmN//lbxcTvWsU6NQd4IihLt89VkbX+14IGc9sVApSxd/w==", + "requires": {} + }, "react-native-document-picker": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-8.1.1.tgz", diff --git a/package.json b/package.json index 162bd7edfc03..bfcb0c59dcff 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "react-native-blob-util": "^0.16.2", "react-native-collapsible": "^1.6.0", "react-native-config": "^1.4.5", + "react-native-device-info": "^10.3.0", "react-native-document-picker": "^8.0.0", "react-native-fast-image": "^8.6.3", "react-native-gesture-handler": "2.6.0", From b6e2b21c1a08abb5b3028313f29dde2b52df86e7 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:20:35 -0800 Subject: [PATCH 002/108] Upgrade urbanairship-react-native --- ios/Podfile.lock | 30 +++++++++++++++--------------- package-lock.json | 14 +++++++------- package.json | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index dc45b4b7d650..70733341159e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,18 +1,18 @@ PODS: - - Airship (16.8.0): - - Airship/Automation (= 16.8.0) - - Airship/Basement (= 16.8.0) - - Airship/Core (= 16.8.0) - - Airship/ExtendedActions (= 16.8.0) - - Airship/MessageCenter (= 16.8.0) - - Airship/Automation (16.8.0): + - Airship (16.10.7): + - Airship/Automation (= 16.10.7) + - Airship/Basement (= 16.10.7) + - Airship/Core (= 16.10.7) + - Airship/ExtendedActions (= 16.10.7) + - Airship/MessageCenter (= 16.10.7) + - Airship/Automation (16.10.7): - Airship/Core - - Airship/Basement (16.8.0) - - Airship/Core (16.8.0): + - Airship/Basement (16.10.7) + - Airship/Core (16.10.7): - Airship/Basement - - Airship/ExtendedActions (16.8.0): + - Airship/ExtendedActions (16.10.7): - Airship/Core - - Airship/MessageCenter (16.8.0): + - Airship/MessageCenter (16.10.7): - Airship/Core - boost (1.76.0) - CocoaAsyncSocket (7.6.5) @@ -641,8 +641,8 @@ PODS: - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - SocketRocket (0.6.0) - - urbanairship-react-native (14.4.1): - - Airship (= 16.8.0) + - urbanairship-react-native (14.6.1): + - Airship (= 16.10.7) - React-Core - Yoga (1.14.0) - YogaKit (1.18.1): @@ -934,7 +934,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Airship: 4657c3d5118441240e04674d9445cbd6e363c956 + Airship: fbff646723323c58e3871cd30488612ca373f597 boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 @@ -1039,7 +1039,7 @@ SPEC CHECKSUMS: SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 - urbanairship-react-native: 7e2e9a84c541b1d04798e51f7f390a2d5806eac0 + urbanairship-react-native: fe4d169332546a0efd348a009aa490dc36ff815e Yoga: f77f6497bccebdcbc8efb03dbf83eadfdec6d104 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/package-lock.json b/package-lock.json index dd6b9c5b764e..f2f82c1340bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,7 +90,7 @@ "semver": "^7.3.8", "shim-keyboard-event-key": "^1.0.3", "underscore": "^1.13.1", - "urbanairship-react-native": "^14.3.1" + "urbanairship-react-native": "^14.6.1" }, "devDependencies": { "@actions/core": "1.10.0", @@ -40994,9 +40994,9 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/urbanairship-react-native": { - "version": "14.4.1", - "resolved": "https://registry.npmjs.org/urbanairship-react-native/-/urbanairship-react-native-14.4.1.tgz", - "integrity": "sha512-7CFEszUR5DZdmx4YfipiDzbKEF72cBCaEh3gZHjwtGY7gsctKBx057+V5b5988vM+UghHbiCGE5fHgWs2fQMUQ==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/urbanairship-react-native/-/urbanairship-react-native-14.6.1.tgz", + "integrity": "sha512-ddL3ZWZnhwCja9oMpq7YHEyuqca1IH34MtMm24w1SePzGRhcVAvKOe/lncIB1FAK6QyjG0pkPT5mu3vE/DsPEw==", "peerDependencies": { "react": "*", "react-native": "*" @@ -74114,9 +74114,9 @@ } }, "urbanairship-react-native": { - "version": "14.4.1", - "resolved": "https://registry.npmjs.org/urbanairship-react-native/-/urbanairship-react-native-14.4.1.tgz", - "integrity": "sha512-7CFEszUR5DZdmx4YfipiDzbKEF72cBCaEh3gZHjwtGY7gsctKBx057+V5b5988vM+UghHbiCGE5fHgWs2fQMUQ==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/urbanairship-react-native/-/urbanairship-react-native-14.6.1.tgz", + "integrity": "sha512-ddL3ZWZnhwCja9oMpq7YHEyuqca1IH34MtMm24w1SePzGRhcVAvKOe/lncIB1FAK6QyjG0pkPT5mu3vE/DsPEw==", "requires": {} }, "uri-js": { diff --git a/package.json b/package.json index bfcb0c59dcff..356a248d495d 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "semver": "^7.3.8", "shim-keyboard-event-key": "^1.0.3", "underscore": "^1.13.1", - "urbanairship-react-native": "^14.3.1" + "urbanairship-react-native": "^14.6.1" }, "devDependencies": { "@actions/core": "1.10.0", From 4fc6f2632da301c5e13c6f64dda3be3a2382f512 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:23:02 -0800 Subject: [PATCH 003/108] Add mock for react-native-device-info --- __mocks__/react-native-device-info.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 __mocks__/react-native-device-info.js diff --git a/__mocks__/react-native-device-info.js b/__mocks__/react-native-device-info.js new file mode 100644 index 000000000000..2ba5b6ef85b3 --- /dev/null +++ b/__mocks__/react-native-device-info.js @@ -0,0 +1,3 @@ +import MockDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'; + +export default MockDeviceInfo; From efc4490cb5f948a66a1c14d94fd96e2cf1c16e79 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:24:22 -0800 Subject: [PATCH 004/108] Update urbanairship-react-native mock --- __mocks__/urbanairship-react-native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/__mocks__/urbanairship-react-native.js b/__mocks__/urbanairship-react-native.js index ba380b6d4a72..8eb5e9c14c08 100644 --- a/__mocks__/urbanairship-react-native.js +++ b/__mocks__/urbanairship-react-native.js @@ -21,6 +21,7 @@ const UrbanAirship = { removeAllListeners: jest.fn(), setBadgeNumber: jest.fn(), setForegroundPresentationOptions: jest.fn(), + getNotificationStatus: jest.fn(), }; export default UrbanAirship; From 599be59ed9dc0c61cace23ab0bd1ae78fcafa041 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:25:43 -0800 Subject: [PATCH 005/108] Update CustomNotificationProvider --- .../chat/customairshipextender/CustomNotificationProvider.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java index 9b8f64d51a53..9e4cc0df8f43 100644 --- a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java @@ -45,6 +45,8 @@ public class CustomNotificationProvider extends ReactNotificationProvider { + private final Context context; + // Resize icons to 100 dp x 100 dp private static final int MAX_ICON_SIZE_DPS = 100; @@ -71,6 +73,7 @@ public class CustomNotificationProvider extends ReactNotificationProvider { public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) { super(context, configOptions); + this.context = context; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createAndRegisterNotificationChannel(context); } From cce0558d9ccc7f576b41bf5658b49bfe598202db Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:35:34 -0800 Subject: [PATCH 006/108] Make PushNotification lib have same exports on web and native --- src/libs/Notification/PushNotification/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Notification/PushNotification/index.js b/src/libs/Notification/PushNotification/index.js index c4100583442d..88136ff5dc72 100644 --- a/src/libs/Notification/PushNotification/index.js +++ b/src/libs/Notification/PushNotification/index.js @@ -2,6 +2,7 @@ import NotificationType from './NotificationType'; // Push notifications are only supported on mobile, so we'll just noop here export default { + init: () => {}, register: () => {}, deregister: () => {}, onReceived: () => {}, From 51116a7c4ae20f6d2b109f73d51cd9e5d3d3be65 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:52:48 -0800 Subject: [PATCH 007/108] Create User actions to opt in and out of push notifications --- src/CONST.js | 1 + src/ONYXKEYS.js | 3 +++ src/libs/actions/User.js | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/CONST.js b/src/CONST.js index 2d82c601f3cd..4a671996f2df 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -435,6 +435,7 @@ const CONST = { KYC_MIGRATION: 'expensify_migration_2020_04_28_RunKycVerifications', PREFERRED_EMOJI_SKIN_TONE: 'expensify_preferredEmojiSkinTone', FREQUENTLY_USED_EMOJIS: 'expensify_frequentlyUsedEmojis', + PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', }, DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 8caa4b2997c4..0b80a856bebf 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -76,6 +76,9 @@ export default { // Contains the users's block expiration (if they have one) NVP_BLOCKED_FROM_CONCIERGE: 'private_blockedFromConcierge', + // Does this user have push notifications enabled? + NVP_PUSH_NOTIFICATIONS_ENABLED: 'nvp_pushNotificationsEnabled', + // Plaid data (access tokens, bank accounts ...) PLAID_DATA: 'plaidData', diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 09e7d6873084..1c4b1034149f 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; import moment from 'moment'; +import DeviceInfo from 'react-native-device-info'; import ONYXKEYS from '../../ONYXKEYS'; import * as DeprecatedAPI from '../deprecatedAPI'; import * as API from '../API'; @@ -28,6 +29,12 @@ Onyx.connect({ }, }); +let isUserOptedInToPushNotifications = false; +Onyx.connect({ + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + callback: (val) => isUserOptedInToPushNotifications = val, +}); + /** * Changes a password for a given account * @@ -467,6 +474,47 @@ function generateStatementPDF(period) { }); } +/** + * A private helper function for optInToPushNotifications and optOutOfPushNotifications. + * + * @param {Boolean} isOptingIn + */ +function setPushNotificationOptInStatus(isOptingIn) { + const deviceID = DeviceInfo.getDeviceId(); + const commandName = isOptingIn ? 'OptInToPushNotifications' ? 'OptOutOfPushNotifications'; + const optimisticData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + value: {[deviceID]: isOptingIn}, + }, + ]; + const failureData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + value: {[deviceID]: isUserOptedInToPushNotifications}, + }, + ]; + API.write(commandName, {deviceID}, {optimisticData, failureData}); +} + +/** + * Record that user opted-in to push notifications on the current device. + * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. + */ +function optInToPushNotifications() { + setPushNotificationOptInStatus(true); +} + +/** + * Record that user opted-out from push notifications on the current device. + * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. + */ +function optOutOfPushNotifications() { + setPushNotificationOptInStatus(false); +} + export { updatePassword, closeAccount, @@ -487,4 +535,6 @@ export { deletePaypalMeAddress, addPaypalMeAddress, updateChatPriorityMode, + optInToPushNotifications, + optOutOfPushNotifications, }; From e158dbd7a4cc8bb266cef98c95e6fddc47cd5204 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:53:14 -0800 Subject: [PATCH 008/108] Fix ternary --- src/libs/actions/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 1c4b1034149f..15acf375a847 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -481,7 +481,7 @@ function generateStatementPDF(period) { */ function setPushNotificationOptInStatus(isOptingIn) { const deviceID = DeviceInfo.getDeviceId(); - const commandName = isOptingIn ? 'OptInToPushNotifications' ? 'OptOutOfPushNotifications'; + const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; const optimisticData = [ { onyxMethod: CONST.ONYX.METHOD.MERGE, From 6ddd67cb9531cf49eb13879dc566e7fb49c59609 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:56:26 -0800 Subject: [PATCH 009/108] Use only one user action instead of two --- src/libs/actions/User.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 15acf375a847..00118d0f957a 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -475,7 +475,8 @@ function generateStatementPDF(period) { } /** - * A private helper function for optInToPushNotifications and optOutOfPushNotifications. + * Record that user opted-in or opted-out of push notifications on the current device. + * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. * * @param {Boolean} isOptingIn */ @@ -499,22 +500,6 @@ function setPushNotificationOptInStatus(isOptingIn) { API.write(commandName, {deviceID}, {optimisticData, failureData}); } -/** - * Record that user opted-in to push notifications on the current device. - * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. - */ -function optInToPushNotifications() { - setPushNotificationOptInStatus(true); -} - -/** - * Record that user opted-out from push notifications on the current device. - * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. - */ -function optOutOfPushNotifications() { - setPushNotificationOptInStatus(false); -} - export { updatePassword, closeAccount, @@ -535,6 +520,5 @@ export { deletePaypalMeAddress, addPaypalMeAddress, updateChatPriorityMode, - optInToPushNotifications, - optOutOfPushNotifications, + setPushNotificationOptInStatus, }; From 735e5a9e036dc79973ba35afd9f041a4a9f39609 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 16:58:21 -0800 Subject: [PATCH 010/108] Hook up listeners in the PushNotification library to track push notification opt-in status --- .../PushNotification/index.native.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index b6787858191d..b8c4d0627a8c 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -2,8 +2,17 @@ import _ from 'underscore'; import {AppState} from 'react-native'; import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; import Log from '../../Log'; import NotificationType from './NotificationType'; +import * as User from '../../actions/User'; + +let isUserOptedInToPushNotifications = null; +Onyx.connect({ + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + callback: val => isUserOptedInToPushNotifications = val, +}); const notificationEventActionMap = {}; @@ -45,6 +54,22 @@ function pushNotificationEventCallback(eventType, notification) { action(payload); } +/** + * Check if a user is opted-in to push notifications and update the `pushNotificationsEnabled` NVP accordingly. + */ +function refreshNotificationOptInStatus() { + UrbanAirship.getNotificationStatus() + .then((notificationStatus) => { + const isOptedIn = notificationStatus.airshipOptIn && notificationStatus.systemEnabled; + if (isOptedIn === isUserOptedInToPushNotifications) { + return; + } + + Log.info('[PUSH_NOTIFICATION] Push notification opt-in status changed.', false, {isOptedIn}); + User.setPushNotificationOptInStatus(isOptedIn); + }); +} + /** * Register push notification callbacks. This is separate from namedUser registration because it needs to be executed * from a headless JS process, outside of any react lifecycle. @@ -55,6 +80,11 @@ function pushNotificationEventCallback(eventType, notification) { function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { + // By default, refresh notification opt-in status to true if we receive a notification + if (!isUserOptedInToPushNotifications) { + User.setPushNotificationOptInStatus(true); + } + // If a push notification is received while the app is in foreground, // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. if (AppState.currentState === 'active') { @@ -71,6 +101,9 @@ function init() { pushNotificationEventCallback(EventType.NotificationResponse, event.notification); }); + // Keep track of which users have enabled push notifications via an NVP. + UrbanAirship.addListener(EventType.NotificationOptInStatus, refreshNotificationOptInStatus); + // This statement has effect on iOS only. // It enables the App to display push notifications when the App is in foreground. // By default, the push notifications are silenced on iOS if the App is in foreground. @@ -107,6 +140,9 @@ function register(accountID) { // Regardless of the user's opt-in status, we still want to receive silent push notifications. Log.info(`[PUSH_NOTIFICATIONS] Subscribing to notifications for account ID ${accountID}`); UrbanAirship.setNamedUser(accountID.toString()); + + // Refresh notification opt-in status NVP for the new user. + refreshNotificationOptInStatus(); } /** From 7e0a54a509f5ed5dbc7e047fa85bc7e2c6e77bea Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 17:06:54 -0800 Subject: [PATCH 011/108] Fix JS style --- src/libs/actions/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 00118d0f957a..ffe8cb6ffec5 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -32,7 +32,7 @@ Onyx.connect({ let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => isUserOptedInToPushNotifications = val, + callback: val => isUserOptedInToPushNotifications = val, }); /** From fa2fe5e7c7decd755582764138056154f439b04e Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 25 Jan 2023 17:34:37 -0800 Subject: [PATCH 012/108] Fix Onyx.connect callback to handle updated NVP shape --- src/libs/Notification/PushNotification/index.native.js | 10 ++++++++-- src/libs/actions/User.js | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index b8c4d0627a8c..af880f262ad7 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -8,10 +8,16 @@ import Log from '../../Log'; import NotificationType from './NotificationType'; import * as User from '../../actions/User'; -let isUserOptedInToPushNotifications = null; +let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: val => isUserOptedInToPushNotifications = val, + callback: (val) => { + const mostRecentNVPValue = _.last(val); + if (!_.has(mostRecentNVPValue, 'isEnabled')) { + return; + } + isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; + }, }); const notificationEventActionMap = {}; diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index ffe8cb6ffec5..0e7e477ec708 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -32,7 +32,13 @@ Onyx.connect({ let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: val => isUserOptedInToPushNotifications = val, + callback: (val) => { + const mostRecentNVPValue = _.last(val); + if (!_.has(mostRecentNVPValue, 'isEnabled')) { + return; + } + isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; + }, }); /** From 8941f1121045defd9f466086c29568bee9f7f2c9 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 14:07:42 -0800 Subject: [PATCH 013/108] Fix Onyx.connect to be keyed by deviceID --- src/libs/Notification/PushNotification/index.native.js | 6 +++++- src/libs/actions/User.js | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index af880f262ad7..9368f0bbdd98 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -3,16 +3,20 @@ import {AppState} from 'react-native'; import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; +import DeviceInfo from 'react-native-device-info'; import ONYXKEYS from '../../../ONYXKEYS'; import Log from '../../Log'; import NotificationType from './NotificationType'; import * as User from '../../actions/User'; +const deviceID = DeviceInfo.getDeviceId(); + let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, callback: (val) => { - const mostRecentNVPValue = _.last(val); + const pushNotificationOptInRecord = lodashGet(val, deviceID, []); + const mostRecentNVPValue = _.last(pushNotificationOptInRecord); if (!_.has(mostRecentNVPValue, 'isEnabled')) { return; } diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 0e7e477ec708..9be1b9cdaef7 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -21,6 +21,8 @@ import PusherUtils from '../PusherUtils'; import * as Report from './Report'; import * as ReportActionsUtils from '../ReportActionsUtils'; +const deviceID = DeviceInfo.getDeviceId(); + let currentUserAccountID = ''; Onyx.connect({ key: ONYXKEYS.SESSION, @@ -33,7 +35,8 @@ let isUserOptedInToPushNotifications = false; Onyx.connect({ key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, callback: (val) => { - const mostRecentNVPValue = _.last(val); + const pushNotificationOptInRecord = lodashGet(val, deviceID, []); + const mostRecentNVPValue = _.last(pushNotificationOptInRecord); if (!_.has(mostRecentNVPValue, 'isEnabled')) { return; } @@ -487,7 +490,6 @@ function generateStatementPDF(period) { * @param {Boolean} isOptingIn */ function setPushNotificationOptInStatus(isOptingIn) { - const deviceID = DeviceInfo.getDeviceId(); const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; const optimisticData = [ { From 01048e30ec00d8fd324f19f67a4f7d92d7e1e114 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 14:11:39 -0800 Subject: [PATCH 014/108] Consolidate Onyx connection into PushNotification lib --- .../PushNotification/index.native.js | 8 ++++++++ src/libs/actions/User.js | 16 ++-------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 9368f0bbdd98..4a711912a9a0 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -24,6 +24,13 @@ Onyx.connect({ }, }); +/** + * @returns {Boolean} + */ +function isUserOptedIn() { + return isUserOptedInToPushNotifications; +} + const notificationEventActionMap = {}; /** @@ -213,6 +220,7 @@ function clearNotifications() { } export default { + isUserOptedIn, init, register, deregister, diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 9be1b9cdaef7..4309f30931f4 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -20,6 +20,7 @@ import * as SequentialQueue from '../Network/SequentialQueue'; import PusherUtils from '../PusherUtils'; import * as Report from './Report'; import * as ReportActionsUtils from '../ReportActionsUtils'; +import PushNotification from '../Notification/PushNotification'; const deviceID = DeviceInfo.getDeviceId(); @@ -31,19 +32,6 @@ Onyx.connect({ }, }); -let isUserOptedInToPushNotifications = false; -Onyx.connect({ - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => { - const pushNotificationOptInRecord = lodashGet(val, deviceID, []); - const mostRecentNVPValue = _.last(pushNotificationOptInRecord); - if (!_.has(mostRecentNVPValue, 'isEnabled')) { - return; - } - isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; - }, -}); - /** * Changes a password for a given account * @@ -502,7 +490,7 @@ function setPushNotificationOptInStatus(isOptingIn) { { onyxMethod: CONST.ONYX.METHOD.MERGE, key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: isUserOptedInToPushNotifications}, + value: {[deviceID]: PushNotification.isUserOptedIn()}, }, ]; API.write(commandName, {deviceID}, {optimisticData, failureData}); From 6f89dc0493a0b642235ce64faa2dcc9b848db4f8 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 15:04:57 -0800 Subject: [PATCH 015/108] Fix require cycle --- .../PushNotification/index.native.js | 30 ++----------------- src/libs/actions/User.js | 22 ++++++++++++++ 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 4a711912a9a0..aab1da8beeab 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -2,35 +2,10 @@ import _ from 'underscore'; import {AppState} from 'react-native'; import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; -import Onyx from 'react-native-onyx'; -import DeviceInfo from 'react-native-device-info'; -import ONYXKEYS from '../../../ONYXKEYS'; import Log from '../../Log'; import NotificationType from './NotificationType'; import * as User from '../../actions/User'; -const deviceID = DeviceInfo.getDeviceId(); - -let isUserOptedInToPushNotifications = false; -Onyx.connect({ - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => { - const pushNotificationOptInRecord = lodashGet(val, deviceID, []); - const mostRecentNVPValue = _.last(pushNotificationOptInRecord); - if (!_.has(mostRecentNVPValue, 'isEnabled')) { - return; - } - isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; - }, -}); - -/** - * @returns {Boolean} - */ -function isUserOptedIn() { - return isUserOptedInToPushNotifications; -} - const notificationEventActionMap = {}; /** @@ -78,7 +53,7 @@ function refreshNotificationOptInStatus() { UrbanAirship.getNotificationStatus() .then((notificationStatus) => { const isOptedIn = notificationStatus.airshipOptIn && notificationStatus.systemEnabled; - if (isOptedIn === isUserOptedInToPushNotifications) { + if (isOptedIn === User.isUserOptedIntoPushNotifications()) { return; } @@ -98,7 +73,7 @@ function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { // By default, refresh notification opt-in status to true if we receive a notification - if (!isUserOptedInToPushNotifications) { + if (!User.isUserOptedIntoPushNotifications()) { User.setPushNotificationOptInStatus(true); } @@ -220,7 +195,6 @@ function clearNotifications() { } export default { - isUserOptedIn, init, register, deregister, diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 4309f30931f4..ac3d6d60da14 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -32,6 +32,27 @@ Onyx.connect({ }, }); +let isUserOptedInToPushNotifications = false; +Onyx.connect({ + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + callback: (val) => { + const pushNotificationOptInRecord = lodashGet(val, deviceID, []); + const mostRecentNVPValue = _.last(pushNotificationOptInRecord); + if (!_.has(mostRecentNVPValue, 'isEnabled')) { + return; + } + isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; + }, +}); + +/** + * @returns {Boolean} + */ +function isUserOptedIntoPushNotifications() { + return isUserOptedInToPushNotifications; +} + + /** * Changes a password for a given account * @@ -517,4 +538,5 @@ export { addPaypalMeAddress, updateChatPriorityMode, setPushNotificationOptInStatus, + isUserOptedIntoPushNotifications, }; From 29f15e0383ca75757fc38952902d3464d543550b Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 15:43:07 -0800 Subject: [PATCH 016/108] Use Google Play-compliant deviceID alternative --- src/ONYXKEYS.js | 3 +++ src/libs/actions/App.js | 23 +++++++++++++++++++++ src/libs/actions/SignInRedirect.js | 1 + src/libs/getDeviceID/index.ios.js | 15 ++++++++++++++ src/libs/getDeviceID/index.js | 32 ++++++++++++++++++++++++++++++ src/setup/index.js | 3 +++ 6 files changed, 77 insertions(+) create mode 100644 src/libs/getDeviceID/index.ios.js create mode 100644 src/libs/getDeviceID/index.js diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 0b80a856bebf..99c2fede9fa2 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -15,6 +15,9 @@ export default { // which tab is the leader, and which ones are the followers ACTIVE_CLIENTS: 'activeClients', + // A unique ID for the device + DEVICE_ID: 'deviceID', + // Boolean flag set whenever the sidebar has loaded IS_SIDEBAR_LOADED: 'isSidebarLoaded', diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index 874b45f1fc57..ace9a979568c 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -16,6 +16,7 @@ import ROUTES from '../../ROUTES'; import * as SessionUtils from '../SessionUtils'; import getCurrentUrl from '../Navigation/currentUrl'; import * as Session from './Session'; +import getDeviceID from '../getDeviceID'; let currentUserAccountID; let currentUserEmail = ''; @@ -270,6 +271,27 @@ function openProfile() { Navigation.navigate(ROUTES.SETTINGS_PROFILE); } +/** + * Saves a unique deviceID into Onyx. + */ +function setDeviceID() { + const connectionID = Onyx.connect({ + key: ONYXKEYS.DEVICE_ID, + callback: (deviceID) => { + Onyx.disconnect(connectionID); + if (deviceID) { + Log.info('Found existing deviceID', false, deviceID); + return; + } + + getDeviceID().then((uniqueID) => { + Log.info('Setting new deviceID', false, uniqueID); + Onyx.set(ONYXKEYS.DEVICE_ID, uniqueID); + }); + }, + }); +} + export { setLocale, setSidebarLoaded, @@ -277,4 +299,5 @@ export { openProfile, openApp, reconnectApp, + setDeviceID, }; diff --git a/src/libs/actions/SignInRedirect.js b/src/libs/actions/SignInRedirect.js index c7b1079c04f4..7abcd94337bb 100644 --- a/src/libs/actions/SignInRedirect.js +++ b/src/libs/actions/SignInRedirect.js @@ -29,6 +29,7 @@ function clearStorageAndRedirect(errorMessage) { const keysToPreserve = []; keysToPreserve.push(ONYXKEYS.NVP_PREFERRED_LOCALE); keysToPreserve.push(ONYXKEYS.ACTIVE_CLIENTS); + keysToPreserve.push(ONYXKEYS.DEVICE_ID); // After signing out, set ourselves as offline if we were offline before logging out and we are not forcing it. // If we are forcing offline, ignore it while signed out, otherwise it would require a refresh because there's no way to toggle the switch to go back online while signed out. diff --git a/src/libs/getDeviceID/index.ios.js b/src/libs/getDeviceID/index.ios.js new file mode 100644 index 000000000000..3146ca7bcd6e --- /dev/null +++ b/src/libs/getDeviceID/index.ios.js @@ -0,0 +1,15 @@ +import DeviceInfo from 'react-native-device-info'; + +const deviceID = DeviceInfo.getDeviceId(); + +/** + * Get the unique ID of the current device. This should remain the same even if the user uninstalls and reinstalls the app. + * + * @returns {Promise} + */ +function getDeviceID() { + return DeviceInfo.getUniqueId() + .then(uniqueID => `${deviceID}_${uniqueID}`); +} + +export default getDeviceID; diff --git a/src/libs/getDeviceID/index.js b/src/libs/getDeviceID/index.js new file mode 100644 index 000000000000..b5ff94ef6e53 --- /dev/null +++ b/src/libs/getDeviceID/index.js @@ -0,0 +1,32 @@ +import DeviceInfo from 'react-native-device-info'; +import Str from 'expensify-common'; + +const deviceID = DeviceInfo.getDeviceId(); +const uniqueID = Str.guid(deviceID); + +/** + * Get the "unique ID of the device". Note that the hardware ID provided by react-native-device-info for Android is considered private information, + * so using it without appropriate permissions would cause our app to be unlisted from the Google Play Store: + * + * - https://developer.android.com/training/articles/user-data-ids#kotlin + * = https://support.google.com/googleplay/android-developer/answer/10144311 + * + * Therefore, this deviceID is not truly unique but will be a new GUID each time the app runs. + * + * This GUID should stored in Onyx under ONYXKEYS.DEVICE_ID and preserved on logout, such that the deviceID will only change if: + * + * - The user uninstalls and reinstalls the app (Android/desktop) + * - The user opens the app on a different browser or in an incognito window (web) + * - The user manually clears Onyx data + * + * While this isn't perfect, it's the best we can do without violating Google Play's App Store guidelines. + * It's also just as good as any common web solution, such as this one (which is also reset under the same circumstances): + * https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId + * + * @returns {Promise} + */ +function getDeviceID() { + return Promise.resolve(uniqueID); +} + +export default getDeviceID; diff --git a/src/setup/index.js b/src/setup/index.js index 7cf509e1e3dd..e4ad7c235939 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -4,6 +4,7 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import platformSetup from './platformSetup'; import * as Metrics from '../libs/Metrics'; +import * as App from '../libs/actions/App'; export default function () { /* @@ -40,6 +41,8 @@ export default function () { }, }); + App.setDeviceID(); + // Force app layout to work left to right because our design does not currently support devices using this mode I18nManager.allowRTL(false); I18nManager.forceRTL(false); From 973b551e21198081e01a36d5c62a8191ffd63818 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 16:13:15 -0800 Subject: [PATCH 017/108] Move Device to its own action --- .../PushNotification/permissionTracker.js | 0 src/libs/actions/App.js | 23 --------- .../Device/generateDeviceID}/index.ios.js | 4 +- .../Device/generateDeviceID}/index.js | 4 +- src/libs/actions/Device/index.js | 50 +++++++++++++++++++ src/setup/index.js | 4 +- 6 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 src/libs/Notification/PushNotification/permissionTracker.js rename src/libs/{getDeviceID => actions/Device/generateDeviceID}/index.ios.js (84%) rename src/libs/{getDeviceID => actions/Device/generateDeviceID}/index.js (95%) create mode 100644 src/libs/actions/Device/index.js diff --git a/src/libs/Notification/PushNotification/permissionTracker.js b/src/libs/Notification/PushNotification/permissionTracker.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index ace9a979568c..874b45f1fc57 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -16,7 +16,6 @@ import ROUTES from '../../ROUTES'; import * as SessionUtils from '../SessionUtils'; import getCurrentUrl from '../Navigation/currentUrl'; import * as Session from './Session'; -import getDeviceID from '../getDeviceID'; let currentUserAccountID; let currentUserEmail = ''; @@ -271,27 +270,6 @@ function openProfile() { Navigation.navigate(ROUTES.SETTINGS_PROFILE); } -/** - * Saves a unique deviceID into Onyx. - */ -function setDeviceID() { - const connectionID = Onyx.connect({ - key: ONYXKEYS.DEVICE_ID, - callback: (deviceID) => { - Onyx.disconnect(connectionID); - if (deviceID) { - Log.info('Found existing deviceID', false, deviceID); - return; - } - - getDeviceID().then((uniqueID) => { - Log.info('Setting new deviceID', false, uniqueID); - Onyx.set(ONYXKEYS.DEVICE_ID, uniqueID); - }); - }, - }); -} - export { setLocale, setSidebarLoaded, @@ -299,5 +277,4 @@ export { openProfile, openApp, reconnectApp, - setDeviceID, }; diff --git a/src/libs/getDeviceID/index.ios.js b/src/libs/actions/Device/generateDeviceID/index.ios.js similarity index 84% rename from src/libs/getDeviceID/index.ios.js rename to src/libs/actions/Device/generateDeviceID/index.ios.js index 3146ca7bcd6e..943971c5ba45 100644 --- a/src/libs/getDeviceID/index.ios.js +++ b/src/libs/actions/Device/generateDeviceID/index.ios.js @@ -7,9 +7,9 @@ const deviceID = DeviceInfo.getDeviceId(); * * @returns {Promise} */ -function getDeviceID() { +function generateDeviceID() { return DeviceInfo.getUniqueId() .then(uniqueID => `${deviceID}_${uniqueID}`); } -export default getDeviceID; +export default generateDeviceID; diff --git a/src/libs/getDeviceID/index.js b/src/libs/actions/Device/generateDeviceID/index.js similarity index 95% rename from src/libs/getDeviceID/index.js rename to src/libs/actions/Device/generateDeviceID/index.js index b5ff94ef6e53..738195f6d7ac 100644 --- a/src/libs/getDeviceID/index.js +++ b/src/libs/actions/Device/generateDeviceID/index.js @@ -25,8 +25,8 @@ const uniqueID = Str.guid(deviceID); * * @returns {Promise} */ -function getDeviceID() { +function generateDeviceID() { return Promise.resolve(uniqueID); } -export default getDeviceID; +export default generateDeviceID; diff --git a/src/libs/actions/Device/index.js b/src/libs/actions/Device/index.js new file mode 100644 index 000000000000..f56b5941c9d0 --- /dev/null +++ b/src/libs/actions/Device/index.js @@ -0,0 +1,50 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; +import Log from '../../Log'; +import generateDeviceID from './generateDeviceID'; + +let deviceID; + +/** + * @returns {Promise} + */ +function getDeviceID() { + return new Promise((resolve) => { + if (deviceID) { + return resolve(deviceID); + } + + const connectionID = Onyx.connect({ + key: ONYXKEYS.DEVICE_ID, + callback: (ID) => { + Onyx.disconnect(connectionID); + deviceID = ID; + return resolve(ID); + }, + }); + }); +} + +/** + * Saves a unique deviceID into Onyx. + */ +function setDeviceID() { + getDeviceID() + .then((existingDeviceID) => { + if (!existingDeviceID) { + return Promise.resolve(); + } + throw new Error(existingDeviceID); + }) + .then(generateDeviceID) + .then((uniqueID) => { + Log.info('Got new deviceID', false, uniqueID); + Onyx.set(ONYXKEYS.DEVICE_ID, uniqueID); + }) + .catch(err => Log.info('Found existing deviceID', false, err.message)); +} + +export { + getDeviceID, + setDeviceID, +}; diff --git a/src/setup/index.js b/src/setup/index.js index e4ad7c235939..c2b380f6485b 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -4,7 +4,7 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import platformSetup from './platformSetup'; import * as Metrics from '../libs/Metrics'; -import * as App from '../libs/actions/App'; +import * as Device from '../libs/actions/Device'; export default function () { /* @@ -41,7 +41,7 @@ export default function () { }, }); - App.setDeviceID(); + Device.setDeviceID(); // Force app layout to work left to right because our design does not currently support devices using this mode I18nManager.allowRTL(false); From 1513b20d52857f36c7fb73f152d0b4ef8a05032d Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 16:33:26 -0800 Subject: [PATCH 018/108] Fix require cycle by moving push notification tracking to separate module --- .../PushNotification/index.native.js | 43 ++++++++------ .../PushNotification/permissionTracker.js | 33 +++++++++++ src/libs/actions/User.js | 59 +++++++------------ 3 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index aab1da8beeab..3dcfb7f31e7d 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -4,6 +4,7 @@ import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; import Log from '../../Log'; import NotificationType from './NotificationType'; +import PermissionTracker from './permissionTracker'; import * as User from '../../actions/User'; const notificationEventActionMap = {}; @@ -50,10 +51,16 @@ function pushNotificationEventCallback(eventType, notification) { * Check if a user is opted-in to push notifications and update the `pushNotificationsEnabled` NVP accordingly. */ function refreshNotificationOptInStatus() { - UrbanAirship.getNotificationStatus() - .then((notificationStatus) => { - const isOptedIn = notificationStatus.airshipOptIn && notificationStatus.systemEnabled; - if (isOptedIn === User.isUserOptedIntoPushNotifications()) { + Promise.all([ + UrbanAirship.getNotificationStatus(), + PermissionTracker.isUserOptedInToPushNotifications(), + ]) + .then(([ + notificationStatusFromAirship, + notificationStatusFromOnyx, + ]) => { + const isOptedIn = notificationStatusFromAirship.airshipOptIn && notificationStatusFromAirship.systemEnabled; + if (isOptedIn === notificationStatusFromOnyx) { return; } @@ -72,19 +79,21 @@ function refreshNotificationOptInStatus() { function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { - // By default, refresh notification opt-in status to true if we receive a notification - if (!User.isUserOptedIntoPushNotifications()) { - User.setPushNotificationOptInStatus(true); - } - - // If a push notification is received while the app is in foreground, - // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. - if (AppState.currentState === 'active') { - Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); - return; - } - - pushNotificationEventCallback(EventType.PushReceived, notification); + PermissionTracker.isUserOptedInToPushNotifications((isUserOptedIntoPushNotifications) => { + // By default, refresh notification opt-in status to true if we receive a notification + if (!isUserOptedIntoPushNotifications) { + User.setPushNotificationOptInStatus(true); + } + + // If a push notification is received while the app is in foreground, + // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. + if (AppState.currentState === 'active') { + Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); + return; + } + + pushNotificationEventCallback(EventType.PushReceived, notification); + }); }); // Note: the NotificationResponse event has a nested PushReceived event, diff --git a/src/libs/Notification/PushNotification/permissionTracker.js b/src/libs/Notification/PushNotification/permissionTracker.js index e69de29bb2d1..5f2616b50364 100644 --- a/src/libs/Notification/PushNotification/permissionTracker.js +++ b/src/libs/Notification/PushNotification/permissionTracker.js @@ -0,0 +1,33 @@ +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; +import * as Device from '../../actions/Device'; + +let isUserOptedInToPushNotifications = false; +const getDeviceIDPromise = Device.getDeviceID() + .then((deviceID) => { + Onyx.connect({ + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + callback: (val) => { + const pushNotificationOptInRecord = lodashGet(val, deviceID, []); + const mostRecentNVPValue = _.last(pushNotificationOptInRecord); + if (!_.has(mostRecentNVPValue, 'isEnabled')) { + return; + } + isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; + }, + }); + }); + +/** + * @returns {Promise} + */ +function isUserOptedIntoPushNotifications() { + return getDeviceIDPromise + .then(() => isUserOptedInToPushNotifications); +} + +export default { + isUserOptedInToPushNotifications, +}; diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index ac3d6d60da14..b16e5c7f9bc9 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -20,7 +20,7 @@ import * as SequentialQueue from '../Network/SequentialQueue'; import PusherUtils from '../PusherUtils'; import * as Report from './Report'; import * as ReportActionsUtils from '../ReportActionsUtils'; -import PushNotification from '../Notification/PushNotification'; +import PushNotificationPermissionTracker from '../Notification/PushNotification/permissionTracker'; const deviceID = DeviceInfo.getDeviceId(); @@ -32,27 +32,6 @@ Onyx.connect({ }, }); -let isUserOptedInToPushNotifications = false; -Onyx.connect({ - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => { - const pushNotificationOptInRecord = lodashGet(val, deviceID, []); - const mostRecentNVPValue = _.last(pushNotificationOptInRecord); - if (!_.has(mostRecentNVPValue, 'isEnabled')) { - return; - } - isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; - }, -}); - -/** - * @returns {Boolean} - */ -function isUserOptedIntoPushNotifications() { - return isUserOptedInToPushNotifications; -} - - /** * Changes a password for a given account * @@ -499,22 +478,25 @@ function generateStatementPDF(period) { * @param {Boolean} isOptingIn */ function setPushNotificationOptInStatus(isOptingIn) { - const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; - const optimisticData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: isOptingIn}, - }, - ]; - const failureData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: PushNotification.isUserOptedIn()}, - }, - ]; - API.write(commandName, {deviceID}, {optimisticData, failureData}); + PushNotificationPermissionTracker.isUserOptedInToPushNotifications() + .then((isUserOptedInToPushNotifications) => { + const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; + const optimisticData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + value: {[deviceID]: isOptingIn}, + }, + ]; + const failureData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + value: {[deviceID]: isUserOptedInToPushNotifications}, + }, + ]; + API.write(commandName, {deviceID}, {optimisticData, failureData}); + }); } export { @@ -538,5 +520,4 @@ export { addPaypalMeAddress, updateChatPriorityMode, setPushNotificationOptInStatus, - isUserOptedIntoPushNotifications, }; From ac8badf019d9a19d887d82bf089168ae953b30da Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 17:23:47 -0800 Subject: [PATCH 019/108] Fix permission tracker export --- src/libs/Notification/PushNotification/index.native.js | 4 ++-- src/libs/Notification/PushNotification/permissionTracker.js | 2 +- src/libs/actions/User.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 3dcfb7f31e7d..ded3f154687a 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -53,7 +53,7 @@ function pushNotificationEventCallback(eventType, notification) { function refreshNotificationOptInStatus() { Promise.all([ UrbanAirship.getNotificationStatus(), - PermissionTracker.isUserOptedInToPushNotifications(), + PermissionTracker.isUserOptedIntoPushNotifications(), ]) .then(([ notificationStatusFromAirship, @@ -79,7 +79,7 @@ function refreshNotificationOptInStatus() { function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { - PermissionTracker.isUserOptedInToPushNotifications((isUserOptedIntoPushNotifications) => { + PermissionTracker.isUserOptedIntoPushNotifications((isUserOptedIntoPushNotifications) => { // By default, refresh notification opt-in status to true if we receive a notification if (!isUserOptedIntoPushNotifications) { User.setPushNotificationOptInStatus(true); diff --git a/src/libs/Notification/PushNotification/permissionTracker.js b/src/libs/Notification/PushNotification/permissionTracker.js index 5f2616b50364..ff9026e54857 100644 --- a/src/libs/Notification/PushNotification/permissionTracker.js +++ b/src/libs/Notification/PushNotification/permissionTracker.js @@ -29,5 +29,5 @@ function isUserOptedIntoPushNotifications() { } export default { - isUserOptedInToPushNotifications, + isUserOptedIntoPushNotifications, }; diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index b16e5c7f9bc9..2314091bf3dc 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -478,7 +478,7 @@ function generateStatementPDF(period) { * @param {Boolean} isOptingIn */ function setPushNotificationOptInStatus(isOptingIn) { - PushNotificationPermissionTracker.isUserOptedInToPushNotifications() + PushNotificationPermissionTracker.isUserOptedIntoPushNotifications() .then((isUserOptedInToPushNotifications) => { const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; const optimisticData = [ From 3b13de7e848e4bbc3862b856e6db69e3137a844f Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 17:45:21 -0800 Subject: [PATCH 020/108] Fix require cycle by moving setPushNotificationOptInStatus out of User --- .../PushNotification/index.native.js | 6 +-- src/libs/actions/PushNotification.js | 44 +++++++++++++++++++ src/libs/actions/User.js | 33 -------------- 3 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 src/libs/actions/PushNotification.js diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index ded3f154687a..9341e0bb9de1 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -4,8 +4,8 @@ import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; import Log from '../../Log'; import NotificationType from './NotificationType'; +import * as PushNotification from '../../actions/PushNotification'; import PermissionTracker from './permissionTracker'; -import * as User from '../../actions/User'; const notificationEventActionMap = {}; @@ -65,7 +65,7 @@ function refreshNotificationOptInStatus() { } Log.info('[PUSH_NOTIFICATION] Push notification opt-in status changed.', false, {isOptedIn}); - User.setPushNotificationOptInStatus(isOptedIn); + PushNotification.setPushNotificationOptInStatus(isOptedIn); }); } @@ -82,7 +82,7 @@ function init() { PermissionTracker.isUserOptedIntoPushNotifications((isUserOptedIntoPushNotifications) => { // By default, refresh notification opt-in status to true if we receive a notification if (!isUserOptedIntoPushNotifications) { - User.setPushNotificationOptInStatus(true); + PushNotification.setPushNotificationOptInStatus(true); } // If a push notification is received while the app is in foreground, diff --git a/src/libs/actions/PushNotification.js b/src/libs/actions/PushNotification.js new file mode 100644 index 000000000000..4c6953d8ad03 --- /dev/null +++ b/src/libs/actions/PushNotification.js @@ -0,0 +1,44 @@ +import CONST from '../../CONST'; +import ONYXKEYS from '../../ONYXKEYS'; +import * as API from '../API'; +import * as Device from './Device'; +import PushNotificationPermissionTracker from '../Notification/PushNotification/permissionTracker'; + +/** + * Record that user opted-in or opted-out of push notifications on the current device. + * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. + * + * @param {Boolean} isOptingIn + */ +function setPushNotificationOptInStatus(isOptingIn) { + Promise.all([ + Device.getDeviceID(), + PushNotificationPermissionTracker.isUserOptedIntoPushNotifications(), + ]) + .then(([ + deviceID, + isUserOptedInToPushNotifications, + ]) => { + const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; + const optimisticData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + value: {[deviceID]: isOptingIn}, + }, + ]; + const failureData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, + value: {[deviceID]: isUserOptedInToPushNotifications}, + }, + ]; + API.write(commandName, {deviceID}, {optimisticData, failureData}); + }); +} + +export { + // eslint-disable-next-line import/prefer-default-export + setPushNotificationOptInStatus, +}; diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 2314091bf3dc..09e7d6873084 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -2,7 +2,6 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; import moment from 'moment'; -import DeviceInfo from 'react-native-device-info'; import ONYXKEYS from '../../ONYXKEYS'; import * as DeprecatedAPI from '../deprecatedAPI'; import * as API from '../API'; @@ -20,9 +19,6 @@ import * as SequentialQueue from '../Network/SequentialQueue'; import PusherUtils from '../PusherUtils'; import * as Report from './Report'; import * as ReportActionsUtils from '../ReportActionsUtils'; -import PushNotificationPermissionTracker from '../Notification/PushNotification/permissionTracker'; - -const deviceID = DeviceInfo.getDeviceId(); let currentUserAccountID = ''; Onyx.connect({ @@ -471,34 +467,6 @@ function generateStatementPDF(period) { }); } -/** - * Record that user opted-in or opted-out of push notifications on the current device. - * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. - * - * @param {Boolean} isOptingIn - */ -function setPushNotificationOptInStatus(isOptingIn) { - PushNotificationPermissionTracker.isUserOptedIntoPushNotifications() - .then((isUserOptedInToPushNotifications) => { - const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; - const optimisticData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: isOptingIn}, - }, - ]; - const failureData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: isUserOptedInToPushNotifications}, - }, - ]; - API.write(commandName, {deviceID}, {optimisticData, failureData}); - }); -} - export { updatePassword, closeAccount, @@ -519,5 +487,4 @@ export { deletePaypalMeAddress, addPaypalMeAddress, updateChatPriorityMode, - setPushNotificationOptInStatus, }; From 6a7c2153414c9606f122862be444bdcf98a8a840 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 17:49:08 -0800 Subject: [PATCH 021/108] Remove awkward newline --- src/libs/Notification/PushNotification/permissionTracker.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/Notification/PushNotification/permissionTracker.js b/src/libs/Notification/PushNotification/permissionTracker.js index ff9026e54857..f9434bdc24fd 100644 --- a/src/libs/Notification/PushNotification/permissionTracker.js +++ b/src/libs/Notification/PushNotification/permissionTracker.js @@ -24,8 +24,7 @@ const getDeviceIDPromise = Device.getDeviceID() * @returns {Promise} */ function isUserOptedIntoPushNotifications() { - return getDeviceIDPromise - .then(() => isUserOptedInToPushNotifications); + return getDeviceIDPromise.then(() => isUserOptedInToPushNotifications); } export default { From 6621f1e3516557ad6e24bf052bacc7f1119625d4 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 17:51:32 -0800 Subject: [PATCH 022/108] Clarify comment --- src/libs/actions/Device/generateDeviceID/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Device/generateDeviceID/index.js b/src/libs/actions/Device/generateDeviceID/index.js index 738195f6d7ac..9b2f3cfa7f98 100644 --- a/src/libs/actions/Device/generateDeviceID/index.js +++ b/src/libs/actions/Device/generateDeviceID/index.js @@ -13,14 +13,14 @@ const uniqueID = Str.guid(deviceID); * * Therefore, this deviceID is not truly unique but will be a new GUID each time the app runs. * - * This GUID should stored in Onyx under ONYXKEYS.DEVICE_ID and preserved on logout, such that the deviceID will only change if: + * This GUID is stored in Onyx under ONYXKEYS.DEVICE_ID and is preserved on logout, such that the deviceID will only change if: * - * - The user uninstalls and reinstalls the app (Android/desktop) - * - The user opens the app on a different browser or in an incognito window (web) + * - The user uninstalls and reinstalls the app (Android/desktop), OR + * - The user opens the app on a different browser or in an incognito window (web), OR * - The user manually clears Onyx data * - * While this isn't perfect, it's the best we can do without violating Google Play's App Store guidelines. - * It's also just as good as any common web solution, such as this one (which is also reset under the same circumstances): + * While this isn't perfect, it's the best we can do without violating the Google Play Store guidelines. + * It's also just as good as any obvious web solution, such as this one (which is also reset under the same circumstances): * https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId * * @returns {Promise} From 4f2ca7bfaf2ff043b0aa730dcdc8bdf7a89f082d Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 18:19:13 -0800 Subject: [PATCH 023/108] Simplify Onyx data to a boolean --- src/ONYXKEYS.js | 2 +- .../PushNotification/permissionTracker.js | 23 ++++--------------- src/libs/actions/PushNotification.js | 8 +++---- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 99c2fede9fa2..eaeadf401516 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -80,7 +80,7 @@ export default { NVP_BLOCKED_FROM_CONCIERGE: 'private_blockedFromConcierge', // Does this user have push notifications enabled? - NVP_PUSH_NOTIFICATIONS_ENABLED: 'nvp_pushNotificationsEnabled', + PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', // Plaid data (access tokens, bank accounts ...) PLAID_DATA: 'plaidData', diff --git a/src/libs/Notification/PushNotification/permissionTracker.js b/src/libs/Notification/PushNotification/permissionTracker.js index f9434bdc24fd..c4baa50f92f6 100644 --- a/src/libs/Notification/PushNotification/permissionTracker.js +++ b/src/libs/Notification/PushNotification/permissionTracker.js @@ -1,30 +1,17 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../../ONYXKEYS'; -import * as Device from '../../actions/Device'; let isUserOptedInToPushNotifications = false; -const getDeviceIDPromise = Device.getDeviceID() - .then((deviceID) => { - Onyx.connect({ - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - callback: (val) => { - const pushNotificationOptInRecord = lodashGet(val, deviceID, []); - const mostRecentNVPValue = _.last(pushNotificationOptInRecord); - if (!_.has(mostRecentNVPValue, 'isEnabled')) { - return; - } - isUserOptedInToPushNotifications = mostRecentNVPValue.isEnabled; - }, - }); - }); +Onyx.connect({ + key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, + callback: val => isUserOptedInToPushNotifications = val, +}); /** * @returns {Promise} */ function isUserOptedIntoPushNotifications() { - return getDeviceIDPromise.then(() => isUserOptedInToPushNotifications); + return isUserOptedInToPushNotifications; } export default { diff --git a/src/libs/actions/PushNotification.js b/src/libs/actions/PushNotification.js index 4c6953d8ad03..56f87dc57b1c 100644 --- a/src/libs/actions/PushNotification.js +++ b/src/libs/actions/PushNotification.js @@ -23,15 +23,15 @@ function setPushNotificationOptInStatus(isOptingIn) { const optimisticData = [ { onyxMethod: CONST.ONYX.METHOD.MERGE, - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: isOptingIn}, + key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, + value: isOptingIn, }, ]; const failureData = [ { onyxMethod: CONST.ONYX.METHOD.MERGE, - key: ONYXKEYS.NVP_PUSH_NOTIFICATIONS_ENABLED, - value: {[deviceID]: isUserOptedInToPushNotifications}, + key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, + value: isUserOptedInToPushNotifications, }, ]; API.write(commandName, {deviceID}, {optimisticData, failureData}); From 77f029d55fc851280a0e28268ac032daa50c846d Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 18:21:30 -0800 Subject: [PATCH 024/108] Fix use of PermissionsTracker --- .../PushNotification/index.native.js | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 9341e0bb9de1..7902c11d921a 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -79,21 +79,22 @@ function refreshNotificationOptInStatus() { function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { - PermissionTracker.isUserOptedIntoPushNotifications((isUserOptedIntoPushNotifications) => { - // By default, refresh notification opt-in status to true if we receive a notification - if (!isUserOptedIntoPushNotifications) { - PushNotification.setPushNotificationOptInStatus(true); - } - - // If a push notification is received while the app is in foreground, - // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. - if (AppState.currentState === 'active') { - Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); - return; - } - - pushNotificationEventCallback(EventType.PushReceived, notification); - }); + PermissionTracker.isUserOptedIntoPushNotifications() + .then((isUserOptedIntoPushNotifications) => { + // By default, refresh notification opt-in status to true if we receive a notification + if (!isUserOptedIntoPushNotifications) { + PushNotification.setPushNotificationOptInStatus(true); + } + + // If a push notification is received while the app is in foreground, + // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. + if (AppState.currentState === 'active') { + Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); + return; + } + + pushNotificationEventCallback(EventType.PushReceived, notification); + }); }); // Note: the NotificationResponse event has a nested PushReceived event, From fb853a3c16032c8939250f125b8600a67a9817ef Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 18:26:38 -0800 Subject: [PATCH 025/108] Remove permissionTracker entirely --- .../PushNotification/index.native.js | 52 +++++++++---------- .../PushNotification/permissionTracker.js | 19 ------- src/libs/actions/PushNotification.js | 18 +++---- 3 files changed, 34 insertions(+), 55 deletions(-) delete mode 100644 src/libs/Notification/PushNotification/permissionTracker.js diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 7902c11d921a..fb69307fe45b 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -1,11 +1,18 @@ import _ from 'underscore'; import {AppState} from 'react-native'; +import Onyx from 'react-native-onyx'; import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; import Log from '../../Log'; import NotificationType from './NotificationType'; import * as PushNotification from '../../actions/PushNotification'; -import PermissionTracker from './permissionTracker'; +import ONYXKEYS from '../../../ONYXKEYS'; + +let isUserOptedInToPushNotifications = false; +Onyx.connect({ + key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, + callback: val => isUserOptedInToPushNotifications = val, +}); const notificationEventActionMap = {}; @@ -51,16 +58,10 @@ function pushNotificationEventCallback(eventType, notification) { * Check if a user is opted-in to push notifications and update the `pushNotificationsEnabled` NVP accordingly. */ function refreshNotificationOptInStatus() { - Promise.all([ - UrbanAirship.getNotificationStatus(), - PermissionTracker.isUserOptedIntoPushNotifications(), - ]) - .then(([ - notificationStatusFromAirship, - notificationStatusFromOnyx, - ]) => { - const isOptedIn = notificationStatusFromAirship.airshipOptIn && notificationStatusFromAirship.systemEnabled; - if (isOptedIn === notificationStatusFromOnyx) { + UrbanAirship.getNotificationStatus() + .then((notificationStatus) => { + const isOptedIn = notificationStatus.airshipOptIn && notificationStatus.systemEnabled; + if (isOptedIn === isUserOptedInToPushNotifications) { return; } @@ -79,22 +80,19 @@ function refreshNotificationOptInStatus() { function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { - PermissionTracker.isUserOptedIntoPushNotifications() - .then((isUserOptedIntoPushNotifications) => { - // By default, refresh notification opt-in status to true if we receive a notification - if (!isUserOptedIntoPushNotifications) { - PushNotification.setPushNotificationOptInStatus(true); - } - - // If a push notification is received while the app is in foreground, - // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. - if (AppState.currentState === 'active') { - Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); - return; - } - - pushNotificationEventCallback(EventType.PushReceived, notification); - }); + // By default, refresh notification opt-in status to true if we receive a notification + if (!isUserOptedInToPushNotifications) { + PushNotification.setPushNotificationOptInStatus(true); + } + + // If a push notification is received while the app is in foreground, + // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. + if (AppState.currentState === 'active') { + Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); + return; + } + + pushNotificationEventCallback(EventType.PushReceived, notification); }); // Note: the NotificationResponse event has a nested PushReceived event, diff --git a/src/libs/Notification/PushNotification/permissionTracker.js b/src/libs/Notification/PushNotification/permissionTracker.js deleted file mode 100644 index c4baa50f92f6..000000000000 --- a/src/libs/Notification/PushNotification/permissionTracker.js +++ /dev/null @@ -1,19 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../../../ONYXKEYS'; - -let isUserOptedInToPushNotifications = false; -Onyx.connect({ - key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, - callback: val => isUserOptedInToPushNotifications = val, -}); - -/** - * @returns {Promise} - */ -function isUserOptedIntoPushNotifications() { - return isUserOptedInToPushNotifications; -} - -export default { - isUserOptedIntoPushNotifications, -}; diff --git a/src/libs/actions/PushNotification.js b/src/libs/actions/PushNotification.js index 56f87dc57b1c..2bc86a878246 100644 --- a/src/libs/actions/PushNotification.js +++ b/src/libs/actions/PushNotification.js @@ -1,8 +1,14 @@ +import Onyx from 'react-native-onyx'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; import * as Device from './Device'; -import PushNotificationPermissionTracker from '../Notification/PushNotification/permissionTracker'; + +let isUserOptedInToPushNotifications = false; +Onyx.connect({ + key: ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED, + callback: val => isUserOptedInToPushNotifications = val, +}); /** * Record that user opted-in or opted-out of push notifications on the current device. @@ -11,14 +17,8 @@ import PushNotificationPermissionTracker from '../Notification/PushNotification/ * @param {Boolean} isOptingIn */ function setPushNotificationOptInStatus(isOptingIn) { - Promise.all([ - Device.getDeviceID(), - PushNotificationPermissionTracker.isUserOptedIntoPushNotifications(), - ]) - .then(([ - deviceID, - isUserOptedInToPushNotifications, - ]) => { + Device.getDeviceID() + .then((deviceID) => { const commandName = isOptingIn ? 'OptInToPushNotifications' : 'OptOutOfPushNotifications'; const optimisticData = [ { From 1470b019c9ebc555402700662b5b555affa4e835 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 18:32:31 -0800 Subject: [PATCH 026/108] Improve device comment again --- src/libs/actions/Device/generateDeviceID/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Device/generateDeviceID/index.js b/src/libs/actions/Device/generateDeviceID/index.js index 9b2f3cfa7f98..4914dc3f0fe3 100644 --- a/src/libs/actions/Device/generateDeviceID/index.js +++ b/src/libs/actions/Device/generateDeviceID/index.js @@ -8,16 +8,16 @@ const uniqueID = Str.guid(deviceID); * Get the "unique ID of the device". Note that the hardware ID provided by react-native-device-info for Android is considered private information, * so using it without appropriate permissions would cause our app to be unlisted from the Google Play Store: * - * - https://developer.android.com/training/articles/user-data-ids#kotlin - * = https://support.google.com/googleplay/android-developer/answer/10144311 + * - https://developer.android.com/training/articles/user-data-ids#kotlin + * = https://support.google.com/googleplay/android-developer/answer/10144311 * - * Therefore, this deviceID is not truly unique but will be a new GUID each time the app runs. + * Therefore, this deviceID is not truly unique but will be a new GUID each time the app runs (we work around this limitation by saving it in Onyx) * * This GUID is stored in Onyx under ONYXKEYS.DEVICE_ID and is preserved on logout, such that the deviceID will only change if: * - * - The user uninstalls and reinstalls the app (Android/desktop), OR - * - The user opens the app on a different browser or in an incognito window (web), OR - * - The user manually clears Onyx data + * - The user uninstalls and reinstalls the app (Android/desktop), OR + * - The user opens the app on a different browser or in an incognito window (web), OR + * - The user manually clears Onyx data * * While this isn't perfect, it's the best we can do without violating the Google Play Store guidelines. * It's also just as good as any obvious web solution, such as this one (which is also reset under the same circumstances): From 1924a4b100c9ed15cb1e1aee077922bece164f6e Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 18:59:24 -0800 Subject: [PATCH 027/108] Add static deviceID for the desktop app --- desktop/ELECTRON_EVENTS.js | 9 +++++---- desktop/contextBridge.js | 16 ++++++++++++++++ desktop/main.js | 3 +++ desktop/package-lock.json | 13 ++++++++++++- desktop/package.json | 3 ++- .../Device/generateDeviceID/index.desktop.js | 12 ++++++++++++ .../actions/Device/generateDeviceID/index.js | 2 +- 7 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 src/libs/actions/Device/generateDeviceID/index.desktop.js diff --git a/desktop/ELECTRON_EVENTS.js b/desktop/ELECTRON_EVENTS.js index f0d9b865b01d..6a808bdb99aa 100644 --- a/desktop/ELECTRON_EVENTS.js +++ b/desktop/ELECTRON_EVENTS.js @@ -1,13 +1,14 @@ const ELECTRON_EVENTS = { + BLUR: 'blur', + FOCUS: 'focus', + LOCALE_UPDATED: 'locale-updated', + REQUEST_DEVICE_ID: 'requestDeviceID', + REQUEST_FOCUS_APP: 'requestFocusApp', REQUEST_UPDATE_BADGE_COUNT: 'requestUpdateBadgeCount', REQUEST_VISIBILITY: 'requestVisibility', - REQUEST_FOCUS_APP: 'requestFocusApp', SHOW_KEYBOARD_SHORTCUTS_MODAL: 'show-keyboard-shortcuts-modal', START_UPDATE: 'start-update', UPDATE_DOWNLOADED: 'update-downloaded', - FOCUS: 'focus', - BLUR: 'blur', - LOCALE_UPDATED: 'locale-updated', }; module.exports = ELECTRON_EVENTS; diff --git a/desktop/contextBridge.js b/desktop/contextBridge.js index f065c4caac21..5acd424142d1 100644 --- a/desktop/contextBridge.js +++ b/desktop/contextBridge.js @@ -6,6 +6,7 @@ const { const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ + ELECTRON_EVENTS.REQUEST_DEVICE_ID, ELECTRON_EVENTS.REQUEST_FOCUS_APP, ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, ELECTRON_EVENTS.REQUEST_VISIBILITY, @@ -59,6 +60,21 @@ contextBridge.exposeInMainWorld('electron', { return ipcRenderer.sendSync(channel, data); }, + /** + * Wait for an event to be emitted by the main process and sent to the renderer process. + * + * @param {String} channel + * @param {*} args + * @returns {Promise} + */ + invoke: (channel, ...args) => { + if (!_.contains(WHITELIST_CHANNELS_RENDERER_TO_MAIN, channel)) { + throw new Error(getErrorMessage(channel)); + } + + return ipcRenderer.invoke(channel, ...args); + }, + /** * Set up a listener for events emitted from the main process and sent to the renderer process. * diff --git a/desktop/main.js b/desktop/main.js index d4bfd9dd2fe1..9381ff0e2dfe 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -12,6 +12,7 @@ const serve = require('electron-serve'); const contextMenu = require('electron-context-menu'); const {autoUpdater} = require('electron-updater'); const log = require('electron-log'); +const {machineId} = require('node-machine-id'); const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); const checkForUpdates = require('../src/libs/checkForUpdates'); const CONFIG = require('../src/CONFIG').default; @@ -282,6 +283,8 @@ const mainWindow = (() => { titleBarStyle: 'hidden', }); + ipcMain.handle(ELECTRON_EVENTS.REQUEST_DEVICE_ID, () => machineId()); + /* * The default origin of our Electron app is app://- instead of https://new.expensify.com or https://staging.new.expensify.com * This causes CORS errors because the referer and origin headers are wrong and the API responds with an Access-Control-Allow-Origin that doesn't match app://- diff --git a/desktop/package-lock.json b/desktop/package-lock.json index df18cde3a3d1..abc1299154ef 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,7 +10,8 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4" + "electron-updater": "^4.3.4", + "node-machine-id": "^1.1.12" } }, "node_modules/@types/semver": { @@ -307,6 +308,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -655,6 +661,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", diff --git a/desktop/package.json b/desktop/package.json index a31d0db4e5bd..45283a260970 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,7 +7,8 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4" + "electron-updater": "^4.3.4", + "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", "license": "MIT", diff --git a/src/libs/actions/Device/generateDeviceID/index.desktop.js b/src/libs/actions/Device/generateDeviceID/index.desktop.js new file mode 100644 index 000000000000..26de25e326e8 --- /dev/null +++ b/src/libs/actions/Device/generateDeviceID/index.desktop.js @@ -0,0 +1,12 @@ +import ELECTRON_EVENTS from '../../../../../desktop/ELECTRON_EVENTS'; + +/** + * Get the unique ID of the current device. This should remain the same even if the user uninstalls and reinstalls the app. + * + * @returns {Promise} + */ +function generateDeviceID() { + return window.electron.invoke(ELECTRON_EVENTS.REQUEST_DEVICE_ID); +} + +export default generateDeviceID; diff --git a/src/libs/actions/Device/generateDeviceID/index.js b/src/libs/actions/Device/generateDeviceID/index.js index 4914dc3f0fe3..b1f0eb566832 100644 --- a/src/libs/actions/Device/generateDeviceID/index.js +++ b/src/libs/actions/Device/generateDeviceID/index.js @@ -15,7 +15,7 @@ const uniqueID = Str.guid(deviceID); * * This GUID is stored in Onyx under ONYXKEYS.DEVICE_ID and is preserved on logout, such that the deviceID will only change if: * - * - The user uninstalls and reinstalls the app (Android/desktop), OR + * - The user uninstalls and reinstalls the app (Android), OR * - The user opens the app on a different browser or in an incognito window (web), OR * - The user manually clears Onyx data * From ae22deef722d9cc6cd4c068326d2b854244bd4ce Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 26 Jan 2023 19:13:58 -0800 Subject: [PATCH 028/108] Separate out Android and web deviceID implementations --- .../Device/generateDeviceID/index.android.js | 32 +++++++++++++++++++ .../actions/Device/generateDeviceID/index.js | 21 ++++-------- 2 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 src/libs/actions/Device/generateDeviceID/index.android.js diff --git a/src/libs/actions/Device/generateDeviceID/index.android.js b/src/libs/actions/Device/generateDeviceID/index.android.js new file mode 100644 index 000000000000..433f9171306e --- /dev/null +++ b/src/libs/actions/Device/generateDeviceID/index.android.js @@ -0,0 +1,32 @@ +import DeviceInfo from 'react-native-device-info'; +import Str from 'expensify-common'; + +const deviceID = DeviceInfo.getDeviceId(); +const uniqueID = Str.guid(deviceID); + +/** + * Get the "unique ID of the device". Note that the hardware ID provided by react-native-device-info for Android is considered private information, + * so using it without appropriate permissions could cause our app to be unlisted from the Google Play Store: + * + * - https://developer.android.com/training/articles/user-data-ids#kotlin + * = https://support.google.com/googleplay/android-developer/answer/10144311 + * + * Therefore, this deviceID is not truly unique, but will be a new GUID each time the app runs (we work around this limitation by saving it in Onyx). + * + * This GUID is stored in Onyx under ONYXKEYS.DEVICE_ID and is preserved on logout, such that the deviceID will only change if: + * + * - The user uninstalls and reinstalls the app (Android), OR + * - The user manually clears Onyx data + * + * While this isn't perfect, it's the best we can do without violating the Google Play Store guidelines, and it's probably good enough for most real-world users. + * Furthermore, the deviceID prefix is not unique to a specific device, but is likely to change from one type of device to another. + * + * Including this prefix will tell us with a reasonable degree of confidence if the user just uninstalled and reinstalled the app, or if they got a new device. + * + * @returns {Promise} + */ +function generateDeviceID() { + return Promise.resolve(uniqueID); +} + +export default generateDeviceID; diff --git a/src/libs/actions/Device/generateDeviceID/index.js b/src/libs/actions/Device/generateDeviceID/index.js index b1f0eb566832..f330520befdd 100644 --- a/src/libs/actions/Device/generateDeviceID/index.js +++ b/src/libs/actions/Device/generateDeviceID/index.js @@ -1,27 +1,18 @@ -import DeviceInfo from 'react-native-device-info'; import Str from 'expensify-common'; -const deviceID = DeviceInfo.getDeviceId(); -const uniqueID = Str.guid(deviceID); +const uniqueID = Str.guid(); /** - * Get the "unique ID of the device". Note that the hardware ID provided by react-native-device-info for Android is considered private information, - * so using it without appropriate permissions would cause our app to be unlisted from the Google Play Store: - * - * - https://developer.android.com/training/articles/user-data-ids#kotlin - * = https://support.google.com/googleplay/android-developer/answer/10144311 - * - * Therefore, this deviceID is not truly unique but will be a new GUID each time the app runs (we work around this limitation by saving it in Onyx) + * Get the "unique ID of the device". + * Note deviceID is not truly unique but will be a new GUID each time the app runs (we work around this limitation by saving it in Onyx) * * This GUID is stored in Onyx under ONYXKEYS.DEVICE_ID and is preserved on logout, such that the deviceID will only change if: * - * - The user uninstalls and reinstalls the app (Android), OR - * - The user opens the app on a different browser or in an incognito window (web), OR + * - The user opens the app on a different browser or in an incognito window, OR * - The user manually clears Onyx data * - * While this isn't perfect, it's the best we can do without violating the Google Play Store guidelines. - * It's also just as good as any obvious web solution, such as this one (which is also reset under the same circumstances): - * https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId + * While this isn't perfect, it's just as good as any other obvious web solution, such as this one https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId + * which is also different/reset under the same circumstances * * @returns {Promise} */ From 7287f42612ea7c2c08b8dfb208c9ab52b0da7f1e Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Jan 2023 11:33:46 -0800 Subject: [PATCH 029/108] Improve comment on invoke --- desktop/contextBridge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/contextBridge.js b/desktop/contextBridge.js index 5acd424142d1..231e0072c6c9 100644 --- a/desktop/contextBridge.js +++ b/desktop/contextBridge.js @@ -61,7 +61,7 @@ contextBridge.exposeInMainWorld('electron', { }, /** - * Wait for an event to be emitted by the main process and sent to the renderer process. + * Execute a function in the main process and return a promise that resolves with its response. * * @param {String} channel * @param {*} args From 4baf080858e6f2983bf6e39b386b0547c6c83e0e Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Jan 2023 11:37:10 -0800 Subject: [PATCH 030/108] Improve comment in generateDeviceID/index.android.js --- src/libs/actions/Device/generateDeviceID/index.android.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Device/generateDeviceID/index.android.js b/src/libs/actions/Device/generateDeviceID/index.android.js index 433f9171306e..f4967e148d0b 100644 --- a/src/libs/actions/Device/generateDeviceID/index.android.js +++ b/src/libs/actions/Device/generateDeviceID/index.android.js @@ -19,8 +19,8 @@ const uniqueID = Str.guid(deviceID); * - The user manually clears Onyx data * * While this isn't perfect, it's the best we can do without violating the Google Play Store guidelines, and it's probably good enough for most real-world users. - * Furthermore, the deviceID prefix is not unique to a specific device, but is likely to change from one type of device to another. * + * Furthermore, the deviceID prefix is not unique to a specific device, but is likely to change from one type of device to another. * Including this prefix will tell us with a reasonable degree of confidence if the user just uninstalled and reinstalled the app, or if they got a new device. * * @returns {Promise} From 137740d264be44a834a979c7a775870adde0ff3c Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Jan 2023 11:56:38 -0800 Subject: [PATCH 031/108] Fix ReportTest --- __mocks__/urbanairship-react-native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__mocks__/urbanairship-react-native.js b/__mocks__/urbanairship-react-native.js index 8eb5e9c14c08..5bc90f267bf2 100644 --- a/__mocks__/urbanairship-react-native.js +++ b/__mocks__/urbanairship-react-native.js @@ -21,7 +21,7 @@ const UrbanAirship = { removeAllListeners: jest.fn(), setBadgeNumber: jest.fn(), setForegroundPresentationOptions: jest.fn(), - getNotificationStatus: jest.fn(), + getNotificationStatus: () => Promise.resolve({airshipOptIn: false, systemEnabled: false}), }; export default UrbanAirship; From d9d8999576e688f7eadba98dbb31e01a8d335279 Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Jan 2023 12:01:37 -0800 Subject: [PATCH 032/108] Rename index.js to index.website.js --- .../Device/generateDeviceID/{index.js => index.website.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libs/actions/Device/generateDeviceID/{index.js => index.website.js} (100%) diff --git a/src/libs/actions/Device/generateDeviceID/index.js b/src/libs/actions/Device/generateDeviceID/index.website.js similarity index 100% rename from src/libs/actions/Device/generateDeviceID/index.js rename to src/libs/actions/Device/generateDeviceID/index.website.js From 412e6c34908a5781402aa7ecbf1a28d139b0c2ba Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Jan 2023 12:03:37 -0800 Subject: [PATCH 033/108] Fix expensify-common import --- src/libs/actions/Device/generateDeviceID/index.android.js | 2 +- src/libs/actions/Device/generateDeviceID/index.website.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Device/generateDeviceID/index.android.js b/src/libs/actions/Device/generateDeviceID/index.android.js index f4967e148d0b..f61b860bda7d 100644 --- a/src/libs/actions/Device/generateDeviceID/index.android.js +++ b/src/libs/actions/Device/generateDeviceID/index.android.js @@ -1,5 +1,5 @@ import DeviceInfo from 'react-native-device-info'; -import Str from 'expensify-common'; +import Str from 'expensify-common/lib/str'; const deviceID = DeviceInfo.getDeviceId(); const uniqueID = Str.guid(deviceID); diff --git a/src/libs/actions/Device/generateDeviceID/index.website.js b/src/libs/actions/Device/generateDeviceID/index.website.js index f330520befdd..b8abc4734134 100644 --- a/src/libs/actions/Device/generateDeviceID/index.website.js +++ b/src/libs/actions/Device/generateDeviceID/index.website.js @@ -1,4 +1,4 @@ -import Str from 'expensify-common'; +import Str from 'expensify-common/lib/str'; const uniqueID = Str.guid(); From 9bc055800d24e006f5c38ef5ae5bf0b65c38e3ea Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 27 Jan 2023 13:35:38 -0800 Subject: [PATCH 034/108] Fix NetworkTest --- tests/unit/NetworkTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.js index 0a02132af77e..aae95c8f50cd 100644 --- a/tests/unit/NetworkTest.js +++ b/tests/unit/NetworkTest.js @@ -20,6 +20,7 @@ import * as MainQueue from '../../src/libs/Network/MainQueue'; import * as Request from '../../src/libs/Request'; jest.useFakeTimers(); +jest.mock('../../src/libs/Log'); Onyx.init({ keys: ONYXKEYS, From 0b6b42529e05124872b41800e4f7d67d3c0b4a64 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 2 Feb 2023 23:03:14 -0800 Subject: [PATCH 035/108] Remove super bizzare unnecessary import --- src/libs/Notification/PushNotification/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 452fe774753f..2bc7b4ddc5ef 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -148,7 +148,7 @@ function register(accountID) { // When the user logged out and then logged in with a different account // while the app is still in background, we must resubscribe to the report // push notification in order to render the report click behaviour correctly - PushNotification.init(); + init(); Report.subscribeToReportCommentPushNotifications(); } From ba8728fac06702c5cddb2bb9ac923ac568af97a8 Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 3 Feb 2023 10:30:11 -0800 Subject: [PATCH 036/108] Remove context --- .../customairshipextender/CustomNotificationProvider.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java index 72040f9dab22..36d3565f64d2 100644 --- a/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java @@ -45,9 +45,6 @@ import java.util.concurrent.TimeUnit; public class CustomNotificationProvider extends ReactNotificationProvider { - - private final Context context; - // Resize icons to 100 dp x 100 dp private static final int MAX_ICON_SIZE_DPS = 100; @@ -74,7 +71,6 @@ public class CustomNotificationProvider extends ReactNotificationProvider { public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) { super(context, configOptions); - this.context = context; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createAndRegisterNotificationChannel(context); } From db124ad264e15314768c009b05937e51a3b6a43d Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 3 Feb 2023 10:40:36 -0800 Subject: [PATCH 037/108] Clarify comments around PUSH_NOTIFICATIONS_ENABLED --- src/ONYXKEYS.js | 2 +- src/libs/Notification/PushNotification/index.native.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 912aadceabf7..fbc386d34e26 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -79,7 +79,7 @@ export default { // Contains the users's block expiration (if they have one) NVP_BLOCKED_FROM_CONCIERGE: 'private_blockedFromConcierge', - // Does this user have push notifications enabled? + // Does this user have push notifications enabled for this device? PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', // Plaid data (access tokens, bank accounts ...) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 2bc7b4ddc5ef..8e3c0283a01e 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -56,7 +56,7 @@ function pushNotificationEventCallback(eventType, notification) { } /** - * Check if a user is opted-in to push notifications and update the `pushNotificationsEnabled` NVP accordingly. + * Check if a user is opted-in to push notifications on this device and update the `pushNotificationsEnabled` NVP accordingly. */ function refreshNotificationOptInStatus() { UrbanAirship.getNotificationStatus() From 8fe9141b94ebeb692dbf1cce3ef79356d285f5f6 Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 3 Feb 2023 10:41:28 -0800 Subject: [PATCH 038/108] Remove server-side assumption from comment --- src/libs/actions/PushNotification.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/PushNotification.js b/src/libs/actions/PushNotification.js index 2bc86a878246..168b4fb9e169 100644 --- a/src/libs/actions/PushNotification.js +++ b/src/libs/actions/PushNotification.js @@ -12,7 +12,6 @@ Onyx.connect({ /** * Record that user opted-in or opted-out of push notifications on the current device. - * NOTE: This is purely for record-keeping purposes, and does not affect whether our server will attempt to send notifications to this user. * * @param {Boolean} isOptingIn */ From 5f0921d2f011ccc159870cbad126da0dff9e9f5c Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 7 Feb 2023 14:41:42 -0300 Subject: [PATCH 039/108] rename lastActionCreated to lastVisibleActionCreated --- src/libs/E2E/apiMocks/openApp.js | 50 +++++++++++++------------- src/libs/E2E/apiMocks/openReport.js | 2 +- src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.js | 8 ++--- src/libs/SidebarUtils.js | 8 ++--- src/libs/actions/IOU.js | 4 +-- src/libs/actions/Report.js | 8 ++--- src/pages/home/sidebar/SidebarLinks.js | 2 +- src/pages/reportPropTypes.js | 2 +- tests/actions/ReportTest.js | 6 ++-- tests/ui/UnreadIndicatorsTest.js | 6 ++-- tests/unit/OptionsListUtilsTest.js | 34 +++++++++--------- tests/unit/SidebarFilterTest.js | 18 +++++----- tests/unit/SidebarOrderTest.js | 14 ++++---- tests/utils/LHNTestUtils.js | 6 ++-- 15 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/libs/E2E/apiMocks/openApp.js b/src/libs/E2E/apiMocks/openApp.js index 8ebea45e79d0..03aa99c56a5b 100644 --- a/src/libs/E2E/apiMocks/openApp.js +++ b/src/libs/E2E/apiMocks/openApp.js @@ -1739,7 +1739,7 @@ export default () => ({ isPinned: true, lastReadTimestamp: 1671126234191, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-03 06:45:00', + lastVisibleActionCreated: '2022-08-03 06:45:00', lastMessageTimestamp: 1659509100000, lastMessageText: 'You can easily track, approve, and pay bills in Expensify with your custom compa', lastActorEmail: 'concierge@expensify.com', @@ -1768,7 +1768,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669129206943, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-11-03 20:30:55.599', + lastVisibleActionCreated: '2022-11-03 20:30:55.599', lastMessageTimestamp: 1667507455599, lastMessageText: '', lastActorEmail: 'applausetester@applause.expensifail.com', @@ -1793,7 +1793,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671205050152, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-02 20:03:42', + lastVisibleActionCreated: '2022-08-02 20:03:42', lastMessageTimestamp: 1659470622000, lastMessageText: 'Requested \u20b41.67 from applausetester+perf2@applause.expensifail.com', lastActorEmail: 'applausetester+ihchat4@applause.expensifail.com', @@ -1818,7 +1818,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671210740419, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-11-04 21:18:00.038', + lastVisibleActionCreated: '2022-11-04 21:18:00.038', lastMessageTimestamp: 1667596680038, lastMessageText: 'Cancelled the \u20b440.00 request', lastActorEmail: 'fake3@gmail.com', @@ -1846,7 +1846,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671209362667, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-01 20:48:16', + lastVisibleActionCreated: '2022-08-01 20:48:16', lastMessageTimestamp: 1659386896000, lastMessageText: 'applausetester+perf2@applause.expensifail.com', lastActorEmail: 'fake3@gmail.com', @@ -1878,7 +1878,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671470568415, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-01 20:49:11', + lastVisibleActionCreated: '2022-08-01 20:49:11', lastMessageTimestamp: 1659386951000, lastMessageText: 'Say hello\ud83d\ude10', lastActorEmail: 'fake3@gmail.com', @@ -1904,7 +1904,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1664363369565, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-16 12:30:57', + lastVisibleActionCreated: '2022-08-16 12:30:57', lastMessageTimestamp: 1660653057000, lastMessageText: '', lastActorEmail: 'fake3@gmail.com', @@ -1930,7 +1930,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669197163626, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-02 20:03:41', + lastVisibleActionCreated: '2022-08-02 20:03:41', lastMessageTimestamp: 1659470621000, lastMessageText: 'Split \u20b45.00 with applausetester+perf2@applause.expensifail.com and applauseteste', lastActorEmail: 'applausetester+ihchat4@applause.expensifail.com', @@ -1955,7 +1955,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671214557025, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-12-09 10:17:18.362', + lastVisibleActionCreated: '2022-12-09 10:17:18.362', lastMessageTimestamp: 1670581038362, lastMessageText: 'RR', lastActorEmail: 'applausetester+perf2@applause.expensifail.com', @@ -1981,7 +1981,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669298874528, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-10-12 17:47:45.228', + lastVisibleActionCreated: '2022-10-12 17:47:45.228', lastMessageTimestamp: 1665596865228, lastMessageText: 'STAGING_CHAT_MESSAGE_A2C534B7-3509-416E-A0AD-8463831C29DD', lastActorEmail: 'applausetester+fachat1@applause.expensifail.com', @@ -2006,7 +2006,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 0, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '', + lastVisibleActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', lastActorEmail: '', @@ -2031,7 +2031,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669122367932, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '', + lastVisibleActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', lastActorEmail: '', @@ -2060,7 +2060,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671211239096, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-11-03 20:48:58.815', + lastVisibleActionCreated: '2022-11-03 20:48:58.815', lastMessageTimestamp: 1667508538815, lastMessageText: 'Hi there, thanks for reaching out! How may I help?', lastActorEmail: 'concierge@expensify.com', @@ -2085,7 +2085,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671213666675, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-12-01 08:05:11.009', + lastVisibleActionCreated: '2022-12-01 08:05:11.009', lastMessageTimestamp: 1669881911009, lastMessageText: 'Test', lastActorEmail: 'applausetester+perf2@applause.expensifail.com', @@ -2110,7 +2110,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669300587843, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '', + lastVisibleActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', lastActorEmail: '', @@ -2135,7 +2135,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669881965738, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-10-12 12:20:00.668', + lastVisibleActionCreated: '2022-10-12 12:20:00.668', lastMessageTimestamp: 1665577200668, lastMessageText: 'Room renamed to #jack', lastActorEmail: 'applausetester+pd1005@applause.expensifail.com', @@ -2160,7 +2160,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671214566347, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-12-16 18:14:00.208', + lastVisibleActionCreated: '2022-12-16 18:14:00.208', lastMessageTimestamp: 1671214440208, lastMessageText: 'Requested \u20ac200.00 from Christoph for Essen mit Kunden', lastActorEmail: 'applausetester+perf2@applause.expensifail.com', @@ -2186,7 +2186,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1670955487510, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-11-29 12:38:15.985', + lastVisibleActionCreated: '2022-11-29 12:38:15.985', lastMessageTimestamp: 1669725495985, lastMessageText: 'fff', lastActorEmail: 'fake6@gmail.com', @@ -2212,7 +2212,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669634659097, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-11-29 21:08:00.793', + lastVisibleActionCreated: '2022-11-29 21:08:00.793', lastMessageTimestamp: 1669756080793, lastMessageText: 'Iviviviv8b', lastActorEmail: 'applausetester+0901abb@applause.expensifail.com', @@ -2244,7 +2244,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669129467258, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '', + lastVisibleActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', lastActorEmail: '', @@ -2269,7 +2269,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 0, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '', + lastVisibleActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', lastActorEmail: '', @@ -2301,7 +2301,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671211247254, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-09-15 12:57:59.526', + lastVisibleActionCreated: '2022-09-15 12:57:59.526', lastMessageTimestamp: 1663246679526, lastMessageText: "\ud83d\udc4b Welcome to Expensify! I'm Concierge. Is there anything I can help with? Click ", lastActorEmail: 'concierge@expensify.com', @@ -2332,7 +2332,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669634649909, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-09-16 11:12:46.739', + lastVisibleActionCreated: '2022-09-16 11:12:46.739', lastMessageTimestamp: 1663326766739, lastMessageText: 'Hi there! How can I help?\u00a0', lastActorEmail: 'concierge@expensify.com', @@ -2357,7 +2357,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1669197883208, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-10-12 12:46:43.577', + lastVisibleActionCreated: '2022-10-12 12:46:43.577', lastMessageTimestamp: 1665578803577, lastMessageText: 'Room renamed to #jackd23', lastActorEmail: 'applausetester+pd1005@applause.expensifail.com', @@ -2382,7 +2382,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671205430161, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '', + lastVisibleActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', lastActorEmail: '', diff --git a/src/libs/E2E/apiMocks/openReport.js b/src/libs/E2E/apiMocks/openReport.js index 51076f555497..c43f8d3494c4 100644 --- a/src/libs/E2E/apiMocks/openReport.js +++ b/src/libs/E2E/apiMocks/openReport.js @@ -22,7 +22,7 @@ export default () => ({ isPinned: false, lastReadTimestamp: 1671470568415, lastReadCreated: '1980-01-01 00:00:00.000', - lastActionCreated: '2022-08-01 20:49:11', + lastVisibleActionCreated: '2022-08-01 20:49:11', lastMessageTimestamp: 1659386951000, lastMessageText: 'Say hello\ud83d\ude10', lastActorEmail: 'fake3@gmail.com', diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index a32d86ccb510..5cef148953e0 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -470,7 +470,7 @@ function getOptions(reports, personalDetails, { return CONST.DATE.UNIX_EPOCH; } - return report.lastActionCreated; + return report.lastVisibleActionCreated; }); orderedReports.reverse(); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c2b6d107c326..0f9e79bfa1bf 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -982,7 +982,7 @@ function buildOptimisticChatReport( lastMessageHtml: '', lastMessageText: null, lastReadTime: currentTime, - lastActionCreated: currentTime, + lastVisibleActionCreated: currentTime, notificationPreference, oldPolicyName, ownerEmail: ownerEmail || CONST.REPORT.OWNER_EMAIL_FAKE, @@ -1157,10 +1157,10 @@ function isUnread(report) { return false; } - // lastActionCreated and lastReadTime are both datetime strings and can be compared directly - const lastActionCreated = report.lastActionCreated || ''; + // lastVisibleActionCreated and lastReadTime are both datetime strings and can be compared directly + const lastVisibleActionCreated = report.lastVisibleActionCreated || ''; const lastReadTime = report.lastReadTime || ''; - return lastReadTime < lastActionCreated; + return lastReadTime < lastVisibleActionCreated; } /** diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 3d123f1d909a..ceb8c449b177 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -112,10 +112,10 @@ function getOrderedReportIDs(reportIDFromRoute) { // 2. Outstanding IOUs - Always sorted by iouReportAmount with the largest amounts at the top of the group // 3. Drafts - Always sorted by reportDisplayName // 4. Non-archived reports - // - Sorted by lastActionCreated in default (most recent) view mode + // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode // 5. Archived reports - // - Sorted by lastActionCreated in default (most recent) view mode + // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode let pinnedReports = []; let outstandingIOUReports = []; @@ -152,9 +152,9 @@ function getOrderedReportIDs(reportIDFromRoute) { outstandingIOUReports = lodashOrderBy(outstandingIOUReports, ['iouReportAmount', report => report.displayName.toLowerCase()], ['desc', 'asc']); draftReports = _.sortBy(draftReports, report => report.displayName.toLowerCase()); nonArchivedReports = isInDefaultMode - ? lodashOrderBy(nonArchivedReports, ['lastActionCreated', report => report.displayName.toLowerCase()], ['desc', 'asc']) + ? lodashOrderBy(nonArchivedReports, ['lastVisibleActionCreated', report => report.displayName.toLowerCase()], ['desc', 'asc']) : lodashOrderBy(nonArchivedReports, [report => report.displayName.toLowerCase()], ['asc']); - archivedReports = _.sortBy(archivedReports, report => (isInDefaultMode ? report.lastActionCreated : report.displayName.toLowerCase())); + archivedReports = _.sortBy(archivedReports, report => (isInDefaultMode ? report.lastVisibleActionCreated : report.displayName.toLowerCase())); // For archived reports ensure that most recent reports are at the top by reversing the order of the arrays because underscore will only sort them in ascending order if (isInDefaultMode) { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 292f5377fdfa..bf642a9ab4c8 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -698,7 +698,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType value: { ...chatReport, lastReadTime: DateUtils.getDBTime(), - lastActionCreated: optimisticIOUReportAction.created, + lastVisibleActionCreated: optimisticIOUReportAction.created, lastMessageText: optimisticIOUReportAction.message[0].text, lastMessageHtml: optimisticIOUReportAction.message[0].html, }, @@ -814,7 +814,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho value: { ...chatReport, lastReadTime: DateUtils.getDBTime(), - lastActionCreated: optimisticIOUReportAction.created, + lastVisibleActionCreated: optimisticIOUReportAction.created, lastMessageText: optimisticIOUReportAction.message[0].text, lastMessageHtml: optimisticIOUReportAction.message[0].html, hasOutstandingIOU: false, diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 952920708e80..f70828b8723a 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -220,7 +220,7 @@ function addActions(reportID, text = '', file) { const currentTime = DateUtils.getDBTime(); const optimisticReport = { - lastActionCreated: currentTime, + lastVisibleActionCreated: currentTime, lastMessageText: ReportUtils.formatReportLastMessageText(lastAction.message[0].text), lastActorEmail: currentUserEmail, lastReadTime: currentTime, @@ -692,12 +692,12 @@ function deleteReportComment(reportID, reportAction) { }; // If we are deleting the last visible message, let's find the previous visible one and update the lastMessageText in the LHN. - // Similarly, if we are deleting the last read comment we will want to update the lastActionCreated to use the previous visible message. + // Similarly, if we are deleting the last read comment we will want to update the lastVisibleActionCreated to use the previous visible message. const lastMessageText = ReportActionsUtils.getLastVisibleMessageText(reportID, optimisticReportActions); - const lastActionCreated = ReportActionsUtils.getLastVisibleAction(reportID, optimisticReportActions).created; + const lastVisibleActionCreated = ReportActionsUtils.getLastVisibleAction(reportID, optimisticReportActions).created; const optimisticReport = { lastMessageText, - lastActionCreated, + lastVisibleActionCreated, }; // If the API call fails we must show the original message again, so we revert the message content back to how it was diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index f085595fe0f0..4f1641498da8 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -231,7 +231,7 @@ const chatReportSelector = (report) => { }, lastReadTime: report.lastReadTime, lastMessageText: report.lastMessageText, - lastActionCreated: report.lastActionCreated, + lastVisibleActionCreated: report.lastVisibleActionCreated, iouReportID: report.iouReportID, hasOutstandingIOU: report.hasOutstandingIOU, statusNum: report.statusNum, diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index 7251f7a77609..95a58c26fa8a 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -32,7 +32,7 @@ export default PropTypes.shape({ lastMessageText: PropTypes.string, /** The time of the last message on the report */ - lastActionCreated: PropTypes.string, + lastVisibleActionCreated: PropTypes.string, /** The last time the report was visited */ lastReadTime: PropTypes.string, diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index e0ee0d6be6d6..8e2dffee3e8a 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -114,7 +114,7 @@ describe('actions/Report', () => { value: { reportID: REPORT_ID, notificationPreference: 'always', - lastActionCreated: '2022-11-22 03:48:27.267', + lastVisibleActionCreated: '2022-11-22 03:48:27.267', lastMessageText: 'Testing a comment', lastActorEmail: TEST_USER_LOGIN, }, @@ -246,7 +246,7 @@ describe('actions/Report', () => { notificationPreference: 'always', lastMessageText: 'Comment 1', lastActorEmail: USER_2_LOGIN, - lastActionCreated: reportActionCreatedDate, + lastVisibleActionCreated: reportActionCreatedDate, lastReadTime: DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1), }, }, @@ -375,7 +375,7 @@ describe('actions/Report', () => { notificationPreference: 'always', lastMessageText: 'Current User Comment 3', lastActorEmail: 'test@test.com', - lastActionCreated: reportActionCreatedDate, + lastVisibleActionCreated: reportActionCreatedDate, lastReadTime: reportActionCreatedDate, }, }, diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index dff52a5f7457..78cfa027c14b 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -155,7 +155,7 @@ function signInAndGetAppWithUnreadChat() { reportID: REPORT_ID, reportName: CONST.REPORT.DEFAULT_REPORT_NAME, lastReadTime: reportAction3CreatedDate, - lastActionCreated: reportAction9CreatedDate, + lastVisibleActionCreated: reportAction9CreatedDate, lastMessageText: 'Test', participants: [USER_B_EMAIL], }); @@ -314,7 +314,7 @@ describe('Unread Indicators', () => { reportID: NEW_REPORT_ID, reportName: CONST.REPORT.DEFAULT_REPORT_NAME, lastReadTime: '', - lastActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()), + lastVisibleActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()), lastMessageText: 'Comment 1', participants: [USER_C_EMAIL], }, @@ -551,7 +551,7 @@ describe('Unread Indicators', () => { lastReportAction = {...CollectionUtils.lastItem(reportActions)}; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { lastMessageText: lastReportAction.message[0].text, - lastActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), + lastVisibleActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), lastActorEmail: lastReportAction.actorEmail, reportID: REPORT_ID, }); diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index a55b1389cf76..c48dec63801f 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -10,7 +10,7 @@ describe('OptionsListUtils', () => { const REPORTS = { 1: { lastReadTime: '2021-01-14 11:25:39.295', - lastActionCreated: '2022-11-22 03:26:02.015', + lastVisibleActionCreated: '2022-11-22 03:26:02.015', isPinned: false, reportID: 1, participants: ['tonystark@expensify.com', 'reedrichards@expensify.com'], @@ -19,7 +19,7 @@ describe('OptionsListUtils', () => { }, 2: { lastReadTime: '2021-01-14 11:25:39.296', - lastActionCreated: '2022-11-22 03:26:02.016', + lastVisibleActionCreated: '2022-11-22 03:26:02.016', isPinned: false, reportID: 2, participants: ['peterparker@expensify.com'], @@ -29,7 +29,7 @@ describe('OptionsListUtils', () => { // This is the only report we are pinning in this test 3: { lastReadTime: '2021-01-14 11:25:39.297', - lastActionCreated: '2022-11-22 03:26:02.170', + lastVisibleActionCreated: '2022-11-22 03:26:02.170', isPinned: true, reportID: 3, participants: ['reedrichards@expensify.com'], @@ -37,7 +37,7 @@ describe('OptionsListUtils', () => { }, 4: { lastReadTime: '2021-01-14 11:25:39.298', - lastActionCreated: '2022-11-22 03:26:02.180', + lastVisibleActionCreated: '2022-11-22 03:26:02.180', isPinned: false, reportID: 4, participants: ['tchalla@expensify.com'], @@ -45,7 +45,7 @@ describe('OptionsListUtils', () => { }, 5: { lastReadTime: '2021-01-14 11:25:39.299', - lastActionCreated: '2022-11-22 03:26:02.019', + lastVisibleActionCreated: '2022-11-22 03:26:02.019', isPinned: false, reportID: 5, participants: ['suestorm@expensify.com'], @@ -53,27 +53,27 @@ describe('OptionsListUtils', () => { }, 6: { lastReadTime: '2021-01-14 11:25:39.300', - lastActionCreated: '2022-11-22 03:26:02.020', + lastVisibleActionCreated: '2022-11-22 03:26:02.020', isPinned: false, reportID: 6, participants: ['thor@expensify.com'], reportName: 'Thor', }, - // Note: This report has the largest lastActionCreated + // Note: This report has the largest lastVisibleActionCreated 7: { lastReadTime: '2021-01-14 11:25:39.301', - lastActionCreated: '2022-11-22 03:26:03.999', + lastVisibleActionCreated: '2022-11-22 03:26:03.999', isPinned: false, reportID: 7, participants: ['steverogers@expensify.com'], reportName: 'Captain America', }, - // Note: This report has no lastActionCreated + // Note: This report has no lastVisibleActionCreated 8: { lastReadTime: '2021-01-14 11:25:39.301', - lastActionCreated: '2022-11-22 03:26:02.000', + lastVisibleActionCreated: '2022-11-22 03:26:02.000', isPinned: false, reportID: 8, participants: ['galactus_herald@expensify.com'], @@ -83,7 +83,7 @@ describe('OptionsListUtils', () => { // Note: This report has an IOU 9: { lastReadTime: '2021-01-14 11:25:39.302', - lastActionCreated: '2022-11-22 03:26:02.998', + lastVisibleActionCreated: '2022-11-22 03:26:02.998', isPinned: false, reportID: 9, participants: ['mistersinister@marauders.com'], @@ -95,7 +95,7 @@ describe('OptionsListUtils', () => { // This report is an archived room – it does not have a name and instead falls back on oldPolicyName 10: { lastReadTime: '2021-01-14 11:25:39.200', - lastActionCreated: '2022-11-22 03:26:02.001', + lastVisibleActionCreated: '2022-11-22 03:26:02.001', reportID: 10, isPinned: false, participants: ['tonystark@expensify.com', 'steverogers@expensify.com'], @@ -158,7 +158,7 @@ describe('OptionsListUtils', () => { 11: { lastReadTime: '2021-01-14 11:25:39.302', - lastActionCreated: '2022-11-22 03:26:02.022', + lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: 11, participants: ['concierge@expensify.com'], @@ -170,7 +170,7 @@ describe('OptionsListUtils', () => { ...REPORTS, 12: { lastReadTime: '2021-01-14 11:25:39.302', - lastActionCreated: '2022-11-22 03:26:02.022', + lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: 12, participants: ['chronos@expensify.com'], @@ -182,7 +182,7 @@ describe('OptionsListUtils', () => { ...REPORTS, 13: { lastReadTime: '2021-01-14 11:25:39.302', - lastActionCreated: '2022-11-22 03:26:02.022', + lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: 13, participants: ['receipts@expensify.com'], @@ -269,7 +269,7 @@ describe('OptionsListUtils', () => { // When we filter again but provide a searchValue that should match multiple times results = OptionsListUtils.getSearchOptions(REPORTS, PERSONAL_DETAILS, 'fantastic'); - // Value with latest lastActionCreated should be at the top. + // Value with latest lastVisibleActionCreated should be at the top. expect(results.recentReports.length).toBe(2); expect(results.recentReports[0].text).toBe('Mister Fantastic'); expect(results.recentReports[1].text).toBe('Mister Fantastic'); @@ -342,7 +342,7 @@ describe('OptionsListUtils', () => { // Then several options will be returned and they will be each have the search string in their email or name // even though the currently logged in user matches they should not show. - // Should be ordered by lastActionCreated values. + // Should be ordered by lastVisibleActionCreated values. expect(results.personalDetails.length).toBe(4); expect(results.recentReports.length).toBe(5); expect(results.personalDetails[0].login).toBe('natasharomanoff@expensify.com'); diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 48b91067e896..9fc6fa876296 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -338,7 +338,7 @@ describe('Sidebar', () => { }) // When report3 becomes unread - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`, {lastActionCreated: DateUtils.getDBTime()})) + .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`, {lastVisibleActionCreated: DateUtils.getDBTime()})) // Then all three chats are showing .then(() => { @@ -437,13 +437,13 @@ describe('Sidebar', () => { // When they have unread messages .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${archivedReport.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${archivedPolicyRoomReport.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${archivedUserCreatedPolicyRoomReport.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) // Then they are all visible @@ -483,10 +483,10 @@ describe('Sidebar', () => { // When they both have unread messages .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${policyRoomReport.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${userCreatedPolicyRoomReport.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) // Then both rooms are visible @@ -593,7 +593,7 @@ describe('Sidebar', () => { // Given an archived report with no comments const report = { ...LHNTestUtils.getFakeReport(), - lastActionCreated: '2022-11-22 03:48:27.267', + lastVisibleActionCreated: '2022-11-22 03:48:27.267', statusNum: CONST.REPORT.STATUS.CLOSED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }; @@ -623,7 +623,7 @@ describe('Sidebar', () => { // When the report has comments .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) // Then the report is rendered in the LHN @@ -669,7 +669,7 @@ describe('Sidebar', () => { }) // When the report has a new comment and is now unread - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, {lastActionCreated: DateUtils.getDBTime()})) + .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, {lastVisibleActionCreated: DateUtils.getDBTime()})) // Then the report is rendered in the LHN .then(() => { diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 2b44f2f06243..d33836c4bee7 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -177,9 +177,9 @@ describe('Sidebar', () => { [`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3, })) - // When a new comment is added to report 1 (eg. it's lastActionCreated is updated) + // When a new comment is added to report 1 (eg. it's lastVisibleActionCreated is updated) .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`, { - lastActionCreated: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), })) // Then the order of the reports should be 1 > 3 > 2 @@ -676,18 +676,18 @@ describe('Sidebar', () => { it('orders nonArchived reports by displayName if created timestamps are the same', () => { // Given three nonArchived reports created at the same time - const lastActionCreated = DateUtils.getDBTime(); + const lastVisibleActionCreated = DateUtils.getDBTime(); const report1 = { ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']), - lastActionCreated, + lastVisibleActionCreated, }; const report2 = { ...LHNTestUtils.getFakeReport(['email3@test.com', 'email4@test.com']), - lastActionCreated, + lastVisibleActionCreated, }; const report3 = { ...LHNTestUtils.getFakeReport(['email5@test.com', 'email6@test.com']), - lastActionCreated, + lastVisibleActionCreated, }; const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks('0'); @@ -702,7 +702,7 @@ describe('Sidebar', () => { [`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3, })) - // Then the reports are ordered alphabetically since their lastActionCreated are the same + // Then the reports are ordered alphabetically since their lastVisibleActionCreated are the same .then(() => { const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names'); expect(displayNames).toHaveLength(3); diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index 0079313b9cc0..ed15065785d7 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -73,12 +73,12 @@ let lastFakeReportID = 0; * @returns {Object} */ function getFakeReport(participants = ['email1@test.com', 'email2@test.com'], millisecondsInThePast = 0, isUnread = false) { - const lastActionCreated = DateUtils.getDBTime(Date.now() - millisecondsInThePast); + const lastVisibleActionCreated = DateUtils.getDBTime(Date.now() - millisecondsInThePast); return { reportID: `${++lastFakeReportID}`, reportName: 'Report', - lastActionCreated, - lastReadTime: isUnread ? DateUtils.subtractMillisecondsFromDateTime(lastActionCreated, 1) : lastActionCreated, + lastVisibleActionCreated, + lastReadTime: isUnread ? DateUtils.subtractMillisecondsFromDateTime(lastVisibleActionCreated, 1) : lastVisibleActionCreated, participants, }; } From e55397d528e118a0dd43152c81b16b29870b8b62 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 7 Feb 2023 14:45:58 -0300 Subject: [PATCH 040/108] update onyx migration --- src/libs/migrateOnyx.js | 4 +-- ...ated.js => AddLastVisibleActionCreated.js} | 10 +++---- tests/unit/MigrationTest.js | 28 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) rename src/libs/migrations/{AddLastActionCreated.js => AddLastVisibleActionCreated.js} (75%) diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index 6d1a83be5f40..d8a613845977 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -5,7 +5,7 @@ import RenameActiveClientsKey from './migrations/RenameActiveClientsKey'; import RenamePriorityModeKey from './migrations/RenamePriorityModeKey'; import MoveToIndexedDB from './migrations/MoveToIndexedDB'; import RenameExpensifyNewsStatus from './migrations/RenameExpensifyNewsStatus'; -import AddLastActionCreated from './migrations/AddLastActionCreated'; +import AddLastVisibleActionCreated from './migrations/AddLastVisibleActionCreated'; import KeyReportActionsByReportActionID from './migrations/KeyReportActionsByReportActionID'; export default function () { @@ -20,7 +20,7 @@ export default function () { RenamePriorityModeKey, AddEncryptedAuthToken, RenameExpensifyNewsStatus, - AddLastActionCreated, + AddLastVisibleActionCreated, KeyReportActionsByReportActionID, ]; diff --git a/src/libs/migrations/AddLastActionCreated.js b/src/libs/migrations/AddLastVisibleActionCreated.js similarity index 75% rename from src/libs/migrations/AddLastActionCreated.js rename to src/libs/migrations/AddLastVisibleActionCreated.js index 2449efbaf82d..3b475bf2b130 100644 --- a/src/libs/migrations/AddLastActionCreated.js +++ b/src/libs/migrations/AddLastVisibleActionCreated.js @@ -4,7 +4,7 @@ import Log from '../Log'; import ONYXKEYS from '../../ONYXKEYS'; /** - * This migration adds lastActionCreated to all reports in Onyx, using the value of lastMessageTimestamp + * This migration adds lastVisibleActionCreated to all reports in Onyx, using the value of lastMessageTimestamp * * @returns {Promise} */ @@ -17,7 +17,7 @@ export default function () { Onyx.disconnect(connectionID); const reportsToUpdate = {}; _.each(allReports, (report, key) => { - if (_.has(report, 'lastActionCreated')) { + if (_.has(report, 'lastVisibleActionCreated')) { return; } @@ -26,18 +26,18 @@ export default function () { } reportsToUpdate[key] = report; - reportsToUpdate[key].lastActionCreated = new Date(report.lastMessageTimestamp) + reportsToUpdate[key].lastVisibleActionCreated = new Date(report.lastMessageTimestamp) .toISOString() .replace('T', ' ') .replace('Z', ''); }); if (_.isEmpty(reportsToUpdate)) { - Log.info('[Migrate Onyx] Skipped migration AddLastActionCreated'); + Log.info('[Migrate Onyx] Skipped migration AddLastVisibleActionCreated'); return resolve(); } - Log.info(`[Migrate Onyx] Adding lastActionCreated field to ${_.keys(reportsToUpdate).length} reports`); + Log.info(`[Migrate Onyx] Adding lastVisibleActionCreated field to ${_.keys(reportsToUpdate).length} reports`); // eslint-disable-next-line rulesdir/prefer-actions-set-data return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate) .then(resolve); diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 00ef88973ce3..1caf6a31a87d 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -4,7 +4,7 @@ import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import CONST from '../../src/CONST'; import Log from '../../src/libs/Log'; import getPlatform from '../../src/libs/getPlatform'; -import AddLastActionCreated from '../../src/libs/migrations/AddLastActionCreated'; +import AddLastVisibleActionCreated from '../../src/libs/migrations/AddLastVisibleActionCreated'; import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; import KeyReportActionsByReportActionID from '../../src/libs/migrations/KeyReportActionsByReportActionID'; import ONYXKEYS from '../../src/ONYXKEYS'; @@ -100,8 +100,8 @@ describe('Migrations', () => { }); }); - describe('AddLastActionCreated', () => { - it('Should add lastActionCreated wherever lastMessageTimestamp currently is', () => ( + describe('AddLastVisibleActionCreated', () => { + it('Should add lastVisibleActionCreated wherever lastMessageTimestamp currently is', () => ( Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT}1`]: { lastMessageTimestamp: 1668562273702, @@ -110,9 +110,9 @@ describe('Migrations', () => { lastMessageTimestamp: 1668562314821, }, }) - .then(AddLastActionCreated) + .then(AddLastVisibleActionCreated) .then(() => { - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Adding lastActionCreated field to 2 reports'); + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Adding lastVisibleActionCreated field to 2 reports'); const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, @@ -120,10 +120,10 @@ describe('Migrations', () => { Onyx.disconnect(connectionID); expect(_.keys(allReports).length).toBe(2); _.each(allReports, (report) => { - expect(_.has(report, 'lastActionCreated')).toBe(true); + expect(_.has(report, 'lastVisibleActionCreated')).toBe(true); }); - expect(allReports.report_1.lastActionCreated).toBe('2022-11-16 01:31:13.702'); - expect(allReports.report_2.lastActionCreated).toBe('2022-11-16 01:31:54.821'); + expect(allReports.report_1.lastVisibleActionCreated).toBe('2022-11-16 01:31:13.702'); + expect(allReports.report_2.lastVisibleActionCreated).toBe('2022-11-16 01:31:54.821'); }, }); }) @@ -132,22 +132,22 @@ describe('Migrations', () => { it('Should skip if the report data already has the correct fields', () => ( Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT}1`]: { - lastActionCreated: '2022-11-16 01:31:13.702', + lastVisibleActionCreated: '2022-11-16 01:31:13.702', }, [`${ONYXKEYS.COLLECTION.REPORT}2`]: { - lastActionCreated: '2022-11-16 01:31:54.821', + lastVisibleActionCreated: '2022-11-16 01:31:54.821', }, }) - .then(AddLastActionCreated) + .then(AddLastVisibleActionCreated) .then(() => { - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastActionCreated'); + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastVisibleActionCreated'); }) )); it('Should work even if there is no report data', () => ( - AddLastActionCreated() + AddLastVisibleActionCreated() .then(() => { - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastActionCreated'); + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastVisibleActionCreated'); const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, From dd7ceb9e1ee80dcdbcc950e735b4bd13cf4a1fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Thu, 9 Feb 2023 12:59:21 -0600 Subject: [PATCH 041/108] check if the route is a sub-page of the report screen --- src/libs/ReportUtils.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e29a4f6414ad..1203ed0acd8d 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1456,7 +1456,10 @@ function getReportIDFromDeepLink(url) { route = route.replace('/', ''); } }); - const {reportID} = ROUTES.parseReportRouteParams(route); + const {reportID, isParticipantsRoute} = ROUTES.parseReportRouteParams(route); + if (isParticipantsRoute) { + return ''; + } return reportID; } From 59f113b6f6897f4bc07a0338b9ecb2464f2ac26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Thu, 9 Feb 2023 13:05:43 -0600 Subject: [PATCH 042/108] change variable name to a more accurate name --- src/ROUTES.js | 2 +- src/libs/Navigation/Navigation.js | 4 ++-- src/libs/ReportUtils.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ROUTES.js b/src/ROUTES.js index 469b654b3255..016c636f5bd2 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -142,7 +142,7 @@ export default { const pathSegments = route.split('/'); return { reportID: lodashGet(pathSegments, 1), - isParticipantsRoute: Boolean(lodashGet(pathSegments, 2)), + isSubReportPageRoute: Boolean(lodashGet(pathSegments, 2)), }; }, }; diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js index 447fd481efab..67aab0cacb12 100644 --- a/src/libs/Navigation/Navigation.js +++ b/src/libs/Navigation/Navigation.js @@ -141,8 +141,8 @@ function goBack(shouldOpenDrawer = true) { * @returns {Boolean} */ function isDrawerRoute(route) { - const {reportID, isParticipantsRoute} = ROUTES.parseReportRouteParams(route); - return reportID && !isParticipantsRoute; + const {reportID, isSubReportPageRoute} = ROUTES.parseReportRouteParams(route); + return reportID && !isSubReportPageRoute; } /** diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1203ed0acd8d..34b8947f5fd5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1456,8 +1456,8 @@ function getReportIDFromDeepLink(url) { route = route.replace('/', ''); } }); - const {reportID, isParticipantsRoute} = ROUTES.parseReportRouteParams(route); - if (isParticipantsRoute) { + const {reportID, isSubReportPageRoute} = ROUTES.parseReportRouteParams(route); + if (isSubReportPageRoute) { return ''; } return reportID; From a7d4c3a474499ef176e6775154159590bbd16451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Thu, 9 Feb 2023 13:06:29 -0600 Subject: [PATCH 043/108] add comment --- src/libs/ReportUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 34b8947f5fd5..37ee3a3c5677 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1458,6 +1458,7 @@ function getReportIDFromDeepLink(url) { }); const {reportID, isSubReportPageRoute} = ROUTES.parseReportRouteParams(route); if (isSubReportPageRoute) { + // We allow the Sub-Report deep link routes (settings, details, etc.) to be handled by their respective component pages return ''; } return reportID; From a7f022dd7f177969ba8f0178404dcabf6353c385 Mon Sep 17 00:00:00 2001 From: "T.J" Date: Thu, 9 Feb 2023 15:42:15 -0800 Subject: [PATCH 044/108] Create Your-Expensify-Partner-Manager Article New partner manager article --- .../other/Your-Expensify-Partner-Manager | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/articles/other/Your-Expensify-Partner-Manager diff --git a/docs/articles/other/Your-Expensify-Partner-Manager b/docs/articles/other/Your-Expensify-Partner-Manager new file mode 100644 index 000000000000..df61e136d7a8 --- /dev/null +++ b/docs/articles/other/Your-Expensify-Partner-Manager @@ -0,0 +1,34 @@ +--- +title: Your Expensify Partner Manager +description: Everything you need to know about having an Expensify partner manager +--- + + +# What is a partner manager? +A partner manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. + +Unlike Concierge, a partner manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. + +For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. + +# How do I know if I have a partner manager? +For your firm to be assigned a partner manager, you must complete the ExpensifyApproved! University training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the partner manager. So everyone at your firm must complete the training to receive the maximum benefit. + +You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. + +## How do I contact my partner manager? +You can contact your partner manager by: +- Signing in to new.expensify.com and searching for your partner manager (this is still in a test phase, so it might not work for every customer). +- Replying to or clicking the chat link on any email you get from your partner manager; + +# FAQ +## How do I know if my partner manager is online? +You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours. + +## What if I’m unable to reach my partner manager? +If for some reason, you’re unable to contact your partner manager, perhaps because they’re offline, then you can always reach out to Concierge for assistance. Your partner manager will always get back to you when they’re online again. + +## Can I get on a call with my partner manager? +Of course! You can ask your partner manager to schedule a call whenever you think one might be helpful. Our partner managers can discuss client onboarding strategies, firm wide training, and client setups. + +We recommend continuing to work with Concierge for general support questions, as this team is always online and available to help immediately. From a0926fb0dcfb693d6516560e6fccbd965a28d005 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:58:26 -0500 Subject: [PATCH 045/108] Update Your-Expensify-Partner-Manager --- .../other/Your-Expensify-Partner-Manager | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/articles/other/Your-Expensify-Partner-Manager b/docs/articles/other/Your-Expensify-Partner-Manager index df61e136d7a8..cb6015f27ec4 100644 --- a/docs/articles/other/Your-Expensify-Partner-Manager +++ b/docs/articles/other/Your-Expensify-Partner-Manager @@ -1,34 +1,33 @@ --- title: Your Expensify Partner Manager -description: Everything you need to know about having an Expensify partner manager +description: Everything you need to know about your Expensify Partner Manager --- -# What is a partner manager? -A partner manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. +A Partner Manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner Managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. -Unlike Concierge, a partner manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. +Unlike Concierge, a Partner Manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. -# How do I know if I have a partner manager? -For your firm to be assigned a partner manager, you must complete the ExpensifyApproved! University training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the partner manager. So everyone at your firm must complete the training to receive the maximum benefit. +# How do I know if I have a Partner Manager? +For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University] (https://use.expensify.com/accountants) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit. You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. ## How do I contact my partner manager? You can contact your partner manager by: -- Signing in to new.expensify.com and searching for your partner manager (this is still in a test phase, so it might not work for every customer). -- Replying to or clicking the chat link on any email you get from your partner manager; +- Signing in to new.expensify.com and searching for your Partner Manager +- Replying to or clicking the chat link on any email you get from your Partner Manager # FAQ -## How do I know if my partner manager is online? +## How do I know if my Partner Manager is online? You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours. -## What if I’m unable to reach my partner manager? -If for some reason, you’re unable to contact your partner manager, perhaps because they’re offline, then you can always reach out to Concierge for assistance. Your partner manager will always get back to you when they’re online again. +## What if I’m unable to reach my Partner Manager? +If you’re unable to contact your Partner Manager (i.e., they're out of office for the day) you can reach out to Concierge for assistance. Your Partner Manager will get back to you when they’re online again. -## Can I get on a call with my partner manager? -Of course! You can ask your partner manager to schedule a call whenever you think one might be helpful. Our partner managers can discuss client onboarding strategies, firm wide training, and client setups. +## Can I get on a call with my Partner Manager? +Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Our partner managers can discuss client onboarding strategies, firm wide training, and client setups. -We recommend continuing to work with Concierge for general support questions, as this team is always online and available to help immediately. +We recommend continuing to work with Concierge for **general support questions**, as this team is always online and available to help immediately. From 13a6b1cea3295219957f41b1b44f1639a88994df Mon Sep 17 00:00:00 2001 From: harshdeepjoshi Date: Fri, 10 Feb 2023 23:38:22 +0530 Subject: [PATCH 046/108] Remove autoReset prop for "Mark as unread" option in mini Menu --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index a75e3963e715..a552f74aeb5d 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -165,7 +165,6 @@ export default [ } }, getDescription: () => {}, - autoReset: false, }, { From 526a449ca51f11a1ca7886230d667d7d3ac296a0 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sat, 11 Feb 2023 00:50:31 +0530 Subject: [PATCH 047/108] Do not focus the input on screen exit transition --- src/components/ScreenWrapper/index.js | 12 +++++++++--- src/components/ScreenWrapper/propTypes.js | 6 +++--- src/pages/settings/AddSecondaryLoginPage.js | 2 +- src/pages/settings/PasswordPage.js | 2 +- src/pages/settings/Payments/AddPayPalMePage.js | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index e7ba7e5d0e2e..fc5a043ac637 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -2,6 +2,7 @@ import {View} from 'react-native'; import React from 'react'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import KeyboardAvoidingView from '../KeyboardAvoidingView'; import CONST from '../../CONST'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; @@ -40,10 +41,15 @@ class ScreenWrapper extends React.Component { Navigation.setIsNavigating(true); }); - this.unsubscribeTransitionEnd = this.props.navigation.addListener('transitionEnd', () => { - this.setState({didScreenTransitionEnd: true}); - this.props.onTransitionEnd(); + this.unsubscribeTransitionEnd = this.props.navigation.addListener('transitionEnd', (event) => { Navigation.setIsNavigating(false); + + // Prevent firing the prop callback when user is exiting the page. + if (lodashGet(event, 'data.closing')) { + return; + } + this.setState({didScreenTransitionEnd: true}); + this.props.onEntryTransitionEnd(); }); } diff --git a/src/components/ScreenWrapper/propTypes.js b/src/components/ScreenWrapper/propTypes.js index 46cc9ed6315b..67e1b759ebac 100644 --- a/src/components/ScreenWrapper/propTypes.js +++ b/src/components/ScreenWrapper/propTypes.js @@ -16,8 +16,8 @@ const propTypes = { /** Whether to include padding top */ includePaddingTop: PropTypes.bool, - // Called when navigated Screen's transition is finished. - onTransitionEnd: PropTypes.func, + // Called when navigated Screen's transition is finished. It does not fire when user exit the page. + onEntryTransitionEnd: PropTypes.func, /** The behavior to pass to the KeyboardAvoidingView, requires some trial and error depending on the layout/devices used. * Search 'switch(behavior)' in ./node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js for more context */ @@ -34,7 +34,7 @@ const defaultProps = { style: [], includeSafeAreaPaddingBottom: true, includePaddingTop: true, - onTransitionEnd: () => {}, + onEntryTransitionEnd: () => {}, modal: {}, keyboardAvoidingViewBehavior: 'padding', }; diff --git a/src/pages/settings/AddSecondaryLoginPage.js b/src/pages/settings/AddSecondaryLoginPage.js index 892a75153d55..982736ea1186 100755 --- a/src/pages/settings/AddSecondaryLoginPage.js +++ b/src/pages/settings/AddSecondaryLoginPage.js @@ -91,7 +91,7 @@ class AddSecondaryLoginPage extends Component { render() { return ( { + onEntryTransitionEnd={() => { if (!this.phoneNumberInputRef) { return; } diff --git a/src/pages/settings/PasswordPage.js b/src/pages/settings/PasswordPage.js index 4a6f0c2f459e..dce9a5e82cc5 100755 --- a/src/pages/settings/PasswordPage.js +++ b/src/pages/settings/PasswordPage.js @@ -138,7 +138,7 @@ class PasswordPage extends Component { render() { const shouldShowNewPasswordPrompt = !this.state.errors.newPassword && !this.state.errors.newPasswordSameAsOld; return ( - { + { if (!this.currentPasswordInputRef) { return; } diff --git a/src/pages/settings/Payments/AddPayPalMePage.js b/src/pages/settings/Payments/AddPayPalMePage.js index ba2b5ec99456..c800b49cd804 100644 --- a/src/pages/settings/Payments/AddPayPalMePage.js +++ b/src/pages/settings/Payments/AddPayPalMePage.js @@ -54,7 +54,7 @@ class AddPayPalMePage extends React.Component { render() { return ( - + Date: Fri, 10 Feb 2023 14:23:34 -0500 Subject: [PATCH 048/108] Update Your-Expensify-Partner-Manager --- docs/articles/other/Your-Expensify-Partner-Manager | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/articles/other/Your-Expensify-Partner-Manager b/docs/articles/other/Your-Expensify-Partner-Manager index cb6015f27ec4..73d3a65d933d 100644 --- a/docs/articles/other/Your-Expensify-Partner-Manager +++ b/docs/articles/other/Your-Expensify-Partner-Manager @@ -15,8 +15,8 @@ For your firm to be assigned a Partner Manager, you must complete the [Expensify You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. -## How do I contact my partner manager? -You can contact your partner manager by: +## How do I contact my Partner Manager? +You can contact your Partner Manager by: - Signing in to new.expensify.com and searching for your Partner Manager - Replying to or clicking the chat link on any email you get from your Partner Manager @@ -28,6 +28,6 @@ You will be able to see if they are online via their status in new.expensify.com If you’re unable to contact your Partner Manager (i.e., they're out of office for the day) you can reach out to Concierge for assistance. Your Partner Manager will get back to you when they’re online again. ## Can I get on a call with my Partner Manager? -Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Our partner managers can discuss client onboarding strategies, firm wide training, and client setups. +Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Partner Managers can discuss client onboarding strategies, firm wide training, and client setups. We recommend continuing to work with Concierge for **general support questions**, as this team is always online and available to help immediately. From b2e78b5438de27bacf537cb39ef3cfd92345333c Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:29:52 -0500 Subject: [PATCH 049/108] Update Your-Expensify-Partner-Manager --- docs/articles/other/Your-Expensify-Partner-Manager | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/articles/other/Your-Expensify-Partner-Manager b/docs/articles/other/Your-Expensify-Partner-Manager index 73d3a65d933d..a208d83a72de 100644 --- a/docs/articles/other/Your-Expensify-Partner-Manager +++ b/docs/articles/other/Your-Expensify-Partner-Manager @@ -4,6 +4,7 @@ description: Everything you need to know about your Expensify Partner Manager --- +# What is a Partner Manager? A Partner Manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner Managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. Unlike Concierge, a Partner Manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. @@ -15,12 +16,12 @@ For your firm to be assigned a Partner Manager, you must complete the [Expensify You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. -## How do I contact my Partner Manager? +# How do I contact my Partner Manager? You can contact your Partner Manager by: - Signing in to new.expensify.com and searching for your Partner Manager - Replying to or clicking the chat link on any email you get from your Partner Manager -# FAQ +# FAQs ## How do I know if my Partner Manager is online? You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours. From 470f39bc9cac946c4b2a421d77cd58a7114f95c8 Mon Sep 17 00:00:00 2001 From: "T.J" Date: Fri, 10 Feb 2023 15:14:29 -0800 Subject: [PATCH 050/108] Rename Your-Expensify-Partner-Manager to Your-Expensify-Partner-Manager.md --- ...xpensify-Partner-Manager => Your-Expensify-Partner-Manager.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/articles/other/{Your-Expensify-Partner-Manager => Your-Expensify-Partner-Manager.md} (100%) diff --git a/docs/articles/other/Your-Expensify-Partner-Manager b/docs/articles/other/Your-Expensify-Partner-Manager.md similarity index 100% rename from docs/articles/other/Your-Expensify-Partner-Manager rename to docs/articles/other/Your-Expensify-Partner-Manager.md From 06dfcd12693d54895ac836248a65f665bb281b80 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 13 Feb 2023 18:37:43 -0300 Subject: [PATCH 051/108] update migration --- src/libs/migrations/AddLastVisibleActionCreated.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/migrations/AddLastVisibleActionCreated.js b/src/libs/migrations/AddLastVisibleActionCreated.js index 3b475bf2b130..8ae625bef09c 100644 --- a/src/libs/migrations/AddLastVisibleActionCreated.js +++ b/src/libs/migrations/AddLastVisibleActionCreated.js @@ -21,15 +21,13 @@ export default function () { return; } - if (!_.has(report, 'lastMessageTimestamp')) { + if (!_.has(report, 'lastActionCreated')) { return; } reportsToUpdate[key] = report; - reportsToUpdate[key].lastVisibleActionCreated = new Date(report.lastMessageTimestamp) - .toISOString() - .replace('T', ' ') - .replace('Z', ''); + reportsToUpdate[key].lastVisibleActionCreated = report.lastActionCreated; + reportsToUpdate[key].lastActionCreated = null; }); if (_.isEmpty(reportsToUpdate)) { From 51b0e432132ec14260805b7ee7d1354f449b5e10 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 17:31:41 -0800 Subject: [PATCH 052/108] Add footer to help site, (no images or social icons yet) --- docs/_layouts/default.html | 5 ++ docs/_sass/_breakpoints.scss | 42 ++++++++++++++ docs/_sass/_colors.scss | 1 + docs/_sass/_main.scss | 104 +++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 37a6242a409b..ed3eb411b29d 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -73,6 +73,11 @@

Didn't find what you were looking for?

+ + + diff --git a/docs/_sass/_breakpoints.scss b/docs/_sass/_breakpoints.scss index 3e75c4bad1a7..3c132c4bc23b 100644 --- a/docs/_sass/_breakpoints.scss +++ b/docs/_sass/_breakpoints.scss @@ -13,3 +13,45 @@ $breakpoint-wide: 1600px; @content } } + +// Media Query Breakpoints +@mixin mq($breakpoint) { + $mq-xs: '(max-width: 500px)'; + $mq-sm: '(max-width: 899px)'; + $mq-md: '(min-width: 900px)'; + $mq-lg-thumb: '(min-width: 1150px)'; + $mq-lg: '(min-width: 1200px)'; + $mq-xl: '(min-width: 1650px)'; + $mq-hideDetails: '(max-width: 1150px)'; + $mq-mdOnly: '(min-width: 900px) and (max-width: 1199px)'; + + @if $breakpoint == xs { + @media #{$mq-xs} { + @content; + } + } + + @if $breakpoint == sm { + @media #{$mq-sm} { + @content; + } + } + + @else if $breakpoint == md { + @media #{$mq-md} { + @content; + } + } + + @else if $breakpoint == lg { + @media #{$mq-lg} { + @content; + } + } + + @else if $breakpoint == xl { + @media #{$mq-xl} { + @content; + } + } +} diff --git a/docs/_sass/_colors.scss b/docs/_sass/_colors.scss index baf4df4ddef0..e520cb9f018c 100644 --- a/docs/_sass/_colors.scss +++ b/docs/_sass/_colors.scss @@ -12,6 +12,7 @@ $color-dark-green: #1B5744; $color-darker-green: #002E22; $color-super-dark-green: #061B09; $color-light-blue: #8DC8FF; +$color-text-supporting: #7D8B8F; $color-link: #5AB0FF; $color-link-hovered: #B0D9FF; diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 332b271f7784..28c25b0bc1bb 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -2,6 +2,10 @@ @import 'colors'; @import 'fonts'; +$sidepaneWidthMobile : 500px; +$smallSpacing : 15px; +$mediumSpacing : 32px; + * { margin: 0; padding: 0; @@ -598,3 +602,103 @@ button { overflow: hidden; } } + +.page-footer { + font-size: 15px; + + // @include mq(sm) { + // background: $color-dark url('#{ $images }homepage/expensify-footer-logo--vertical.svg') no-repeat right 120px; + // background-size: 111px 618px; + // } + + h3 { + color: $color-green; + font-size: 17px; + font-weight: 700; + margin-bottom: 16px; + } + + ul { + margin: 0 0 20px !important; + padding: 0; + + li { + list-style-type: none !important; + margin: 0 0 8px; + + a { + color: $color-white; + display: block; + padding: 4px 0; + word-break: break-word; + + &:hover { + color: $color-green; + } + } + } + } + + &__social-icons { + margin: 0 0 20px; + + a { + color: $color-white; + display: inline-block; + + &:hover { + color: $color-green; + } + } + } + + &__fine-print { + color: $color-text-supporting; + font-size: 10px; + + a { + color: $color-green; + } + } + + // Big logo at the bottom + &__logo { + margin-top: 40px; + + img { + display: block; + } + + @include mq (sm) { + display: none; + } + } + + &__wrapper { + margin: 0 auto; + max-width: 1000px; + padding: 64px $mediumSpacing 0; + + @include mq(sm) { + max-width: $sidepaneWidthMobile; + padding: 64px 40px 20px; + } + } + + .columns { + @include mq(md) { + display: flex; + margin-left: (-1 * 16px); + margin-right: (-1 * 16px); + } + } + + .column { + margin-bottom: 40px; + + @include mq(md) { + padding: 16px; + width: 25%; + } + } +} From 88d330568fc76f53fd0f533b9624ff4eabb6379e Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 17:35:56 -0800 Subject: [PATCH 053/108] Use existing breakpoints --- docs/_sass/_breakpoints.scss | 42 ------------------------------------ docs/_sass/_main.scss | 8 +++---- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/docs/_sass/_breakpoints.scss b/docs/_sass/_breakpoints.scss index 3c132c4bc23b..3e75c4bad1a7 100644 --- a/docs/_sass/_breakpoints.scss +++ b/docs/_sass/_breakpoints.scss @@ -13,45 +13,3 @@ $breakpoint-wide: 1600px; @content } } - -// Media Query Breakpoints -@mixin mq($breakpoint) { - $mq-xs: '(max-width: 500px)'; - $mq-sm: '(max-width: 899px)'; - $mq-md: '(min-width: 900px)'; - $mq-lg-thumb: '(min-width: 1150px)'; - $mq-lg: '(min-width: 1200px)'; - $mq-xl: '(min-width: 1650px)'; - $mq-hideDetails: '(max-width: 1150px)'; - $mq-mdOnly: '(min-width: 900px) and (max-width: 1199px)'; - - @if $breakpoint == xs { - @media #{$mq-xs} { - @content; - } - } - - @if $breakpoint == sm { - @media #{$mq-sm} { - @content; - } - } - - @else if $breakpoint == md { - @media #{$mq-md} { - @content; - } - } - - @else if $breakpoint == lg { - @media #{$mq-lg} { - @content; - } - } - - @else if $breakpoint == xl { - @media #{$mq-xl} { - @content; - } - } -} diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 28c25b0bc1bb..7372c4fc8ba4 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -669,7 +669,7 @@ button { display: block; } - @include mq (sm) { + @include maxBreakpoint($breakpoint-tablet) { display: none; } } @@ -679,14 +679,14 @@ button { max-width: 1000px; padding: 64px $mediumSpacing 0; - @include mq(sm) { + @include maxBreakpoint($breakpoint-tablet) { max-width: $sidepaneWidthMobile; padding: 64px 40px 20px; } } .columns { - @include mq(md) { + @include maxBreakpoint($breakpoint-desktop) { display: flex; margin-left: (-1 * 16px); margin-right: (-1 * 16px); @@ -696,7 +696,7 @@ button { .column { margin-bottom: 40px; - @include mq(md) { + @include maxBreakpoint($breakpoint-desktop) { padding: 16px; width: 25%; } From 480d6890e8134f8b4913691a2f3558a3920eaf5a Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 17:41:00 -0800 Subject: [PATCH 054/108] Fix medium breakpoints --- docs/_sass/_main.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 7372c4fc8ba4..b9a7ac31eba5 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -686,7 +686,7 @@ button { } .columns { - @include maxBreakpoint($breakpoint-desktop) { + @include breakpoint($breakpoint-desktop) { display: flex; margin-left: (-1 * 16px); margin-right: (-1 * 16px); @@ -696,7 +696,7 @@ button { .column { margin-bottom: 40px; - @include maxBreakpoint($breakpoint-desktop) { + @include breakpoint($breakpoint-desktop) { padding: 16px; width: 25%; } From defd37e071571431292ba7564975683e06f28239 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 17:57:59 -0800 Subject: [PATCH 055/108] Add icons for docs site, add bottom logo and update bottom padding --- docs/_includes/footer.html | 114 ++++++++++++++++++ docs/_sass/_main.scss | 8 +- .../expensify-footer-logo--vertical.svg | 30 +++++ docs/assets/images/expensify-footer-logo.svg | 30 +++++ docs/assets/images/social-facebook.svg | 7 ++ docs/assets/images/social-instagram.svg | 17 +++ docs/assets/images/social-linkedin.svg | 8 ++ docs/assets/images/social-podcast.svg | 15 +++ docs/assets/images/social-twitter.svg | 9 ++ docs/assets/images/social-youtube.svg | 11 ++ 10 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 docs/_includes/footer.html create mode 100644 docs/assets/images/expensify-footer-logo--vertical.svg create mode 100644 docs/assets/images/expensify-footer-logo.svg create mode 100644 docs/assets/images/social-facebook.svg create mode 100644 docs/assets/images/social-instagram.svg create mode 100644 docs/assets/images/social-linkedin.svg create mode 100644 docs/assets/images/social-podcast.svg create mode 100644 docs/assets/images/social-twitter.svg create mode 100644 docs/assets/images/social-youtube.svg diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html new file mode 100644 index 000000000000..c0f1327a605f --- /dev/null +++ b/docs/_includes/footer.html @@ -0,0 +1,114 @@ + \ No newline at end of file diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index b9a7ac31eba5..e43751b14ff2 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -293,7 +293,7 @@ button { background-color: $color-super-dark-green; min-height: 100vh; margin-left: 0; - padding: 80px 24px 24px 24px; + padding: 80px 24px 0px 24px; @include breakpoint($breakpoint-tablet) { margin-left: 320px; @@ -302,7 +302,7 @@ button { @include breakpoint($breakpoint-desktop) { /* Same as the width of the lhn */ margin-left: 420px; - padding: 52px 68px; + padding: 52px 68px 0 68px; box-sizing: border-box; } @@ -310,7 +310,7 @@ button { margin-left: 420px; /* On wide screens, the padding needs to be equal to the view width, minus the content size, minus the lhn size, divided by two. */ - padding: 52px calc((100vw - 1000px - 420px)/2); + padding: 52px calc((100vw - 1000px - 420px)/2) 0 calc((100vw - 1000px - 420px)/2); } ul, @@ -606,7 +606,7 @@ button { .page-footer { font-size: 15px; - // @include mq(sm) { + // @include maxBreakpoint($breakpoint-tablet) { // background: $color-dark url('#{ $images }homepage/expensify-footer-logo--vertical.svg') no-repeat right 120px; // background-size: 111px 618px; // } diff --git a/docs/assets/images/expensify-footer-logo--vertical.svg b/docs/assets/images/expensify-footer-logo--vertical.svg new file mode 100644 index 000000000000..58dc05ad2944 --- /dev/null +++ b/docs/assets/images/expensify-footer-logo--vertical.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/expensify-footer-logo.svg b/docs/assets/images/expensify-footer-logo.svg new file mode 100644 index 000000000000..e664651b84fd --- /dev/null +++ b/docs/assets/images/expensify-footer-logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/social-facebook.svg b/docs/assets/images/social-facebook.svg new file mode 100644 index 000000000000..3a966653e688 --- /dev/null +++ b/docs/assets/images/social-facebook.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/docs/assets/images/social-instagram.svg b/docs/assets/images/social-instagram.svg new file mode 100644 index 000000000000..79d4aadf374a --- /dev/null +++ b/docs/assets/images/social-instagram.svg @@ -0,0 +1,17 @@ + + + + + + + diff --git a/docs/assets/images/social-linkedin.svg b/docs/assets/images/social-linkedin.svg new file mode 100644 index 000000000000..97a1da8962f4 --- /dev/null +++ b/docs/assets/images/social-linkedin.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/docs/assets/images/social-podcast.svg b/docs/assets/images/social-podcast.svg new file mode 100644 index 000000000000..1366699b6823 --- /dev/null +++ b/docs/assets/images/social-podcast.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/docs/assets/images/social-twitter.svg b/docs/assets/images/social-twitter.svg new file mode 100644 index 000000000000..6a90f95032bb --- /dev/null +++ b/docs/assets/images/social-twitter.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/docs/assets/images/social-youtube.svg b/docs/assets/images/social-youtube.svg new file mode 100644 index 000000000000..834314d27d27 --- /dev/null +++ b/docs/assets/images/social-youtube.svg @@ -0,0 +1,11 @@ + + + + + + From 8ec2b5309746da42b2e193e82604291aad0c8feb Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 18:15:33 -0800 Subject: [PATCH 056/108] add vertical logo --- docs/_layouts/default.html | 1 - docs/_sass/_main.scss | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index ed3eb411b29d..292d4e52b5b3 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -77,7 +77,6 @@

Didn't find what you were looking for?

- diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index e43751b14ff2..3b381ed91c28 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -606,10 +606,11 @@ button { .page-footer { font-size: 15px; - // @include maxBreakpoint($breakpoint-tablet) { - // background: $color-dark url('#{ $images }homepage/expensify-footer-logo--vertical.svg') no-repeat right 120px; - // background-size: 111px 618px; - // } + @include maxBreakpoint($breakpoint-desktop) { + padding-right: 0 !important; + background: url('/assets/images/expensify-footer-logo--vertical.svg') no-repeat right 120px; + background-size: 111px 618px; + } h3 { color: $color-green; @@ -669,7 +670,7 @@ button { display: block; } - @include maxBreakpoint($breakpoint-tablet) { + @include maxBreakpoint($breakpoint-desktop) { display: none; } } From 7217569667f5aced827147f1565e98be0d0dd494 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 14 Feb 2023 11:44:10 +0700 Subject: [PATCH 057/108] fix: adding country code before validate the phone number --- src/libs/OptionsListUtils.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index a32d86ccb510..9fb53c161d9a 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -575,18 +575,19 @@ function getOptions(reports, personalDetails, { let userToInvite = null; const noOptions = (recentReportOptions.length + personalDetailsOptions.length) === 0; const noOptionsMatchExactly = !_.find(personalDetailsOptions.concat(recentReportOptions), option => option.login === searchValue.toLowerCase()); + const login = (Str.isValidPhone(searchValue) && !searchValue.includes('+')) + ? `+${countryCodeByIP}${searchValue}` + : searchValue; if (searchValue && (noOptions || noOptionsMatchExactly) && !isCurrentUser({login: searchValue}) && _.every(selectedOptions, option => option.login !== searchValue) - && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue)) || Str.isValidPhone(searchValue)) + && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue)) || Str.isValidPhone(login)) && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase())) && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) ) { // If the phone number doesn't have an international code then let's prefix it with the // current user's international code based on their IP address. - const login = (Str.isValidPhone(searchValue) && !searchValue.includes('+')) - ? `+${countryCodeByIP}${searchValue}` - : searchValue; + userToInvite = createOption([login], personalDetails, null, reportActions, { showChatPreviewLine, }); From e6c67e155feab7ae5799850e4db3144bd5c15ebf Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 20:45:34 -0800 Subject: [PATCH 058/108] Add social icons and fix margin of vertical logo --- docs/_includes/footer.html | 20 +++++++++++++++----- docs/_sass/_main.scss | 6 +++--- docs/assets/images/social-facebook.svg | 2 +- docs/assets/images/social-instagram.svg | 2 +- docs/assets/images/social-linkedin.svg | 2 +- docs/assets/images/social-podcast.svg | 2 +- docs/assets/images/social-twitter.svg | 2 +- docs/assets/images/social-youtube.svg | 2 +- 8 files changed, 24 insertions(+), 14 deletions(-) diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index c0f1327a605f..5c603e7d5c62 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -81,11 +81,21 @@

Learn more

diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 3b381ed91c28..494881d2f009 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -605,11 +605,12 @@ button { .page-footer { font-size: 15px; + margin: 0px !important; @include maxBreakpoint($breakpoint-desktop) { - padding-right: 0 !important; background: url('/assets/images/expensify-footer-logo--vertical.svg') no-repeat right 120px; background-size: 111px 618px; + margin-right: -25px; } h3 { @@ -620,7 +621,7 @@ button { } ul { - margin: 0 0 20px !important; + margin: 0px !important; padding: 0; li { @@ -641,7 +642,6 @@ button { } &__social-icons { - margin: 0 0 20px; a { color: $color-white; diff --git a/docs/assets/images/social-facebook.svg b/docs/assets/images/social-facebook.svg index 3a966653e688..e10f0b21b4dc 100644 --- a/docs/assets/images/social-facebook.svg +++ b/docs/assets/images/social-facebook.svg @@ -1,7 +1,7 @@ + viewBox="0 0 20 20" fill="#FFF" style="enable-background:new 0 0 20 20;" xml:space="preserve"> diff --git a/docs/assets/images/social-instagram.svg b/docs/assets/images/social-instagram.svg index 79d4aadf374a..848a8563403e 100644 --- a/docs/assets/images/social-instagram.svg +++ b/docs/assets/images/social-instagram.svg @@ -1,7 +1,7 @@ + viewBox="0 0 20 20" fill="#FFF" style="enable-background:new 0 0 20 20;" xml:space="preserve"> + viewBox="0 0 20 20" fill="#FFF" style="enable-background:new 0 0 20 20;" xml:space="preserve"> diff --git a/docs/assets/images/social-podcast.svg b/docs/assets/images/social-podcast.svg index 1366699b6823..b3db63124d2a 100644 --- a/docs/assets/images/social-podcast.svg +++ b/docs/assets/images/social-podcast.svg @@ -1,7 +1,7 @@ + viewBox="0 0 20 20" fill="#FFF" style="enable-background:new 0 0 20 20;" xml:space="preserve"> diff --git a/docs/assets/images/social-twitter.svg b/docs/assets/images/social-twitter.svg index 6a90f95032bb..40465f27185c 100644 --- a/docs/assets/images/social-twitter.svg +++ b/docs/assets/images/social-twitter.svg @@ -1,7 +1,7 @@ + viewBox="0 0 20 20" fill="#FFF" style="enable-background:new 0 0 20 20;" xml:space="preserve"> + viewBox="0 0 20 20" fill="#FFF" style="enable-background:new 0 0 20 20;" xml:space="preserve"> From bbf0b15883f7c1158c5fbb19400fee1039f11929 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Mon, 13 Feb 2023 20:47:34 -0800 Subject: [PATCH 059/108] Align logo against side --- docs/_sass/_main.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 494881d2f009..18981db26a5d 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -605,7 +605,6 @@ button { .page-footer { font-size: 15px; - margin: 0px !important; @include maxBreakpoint($breakpoint-desktop) { background: url('/assets/images/expensify-footer-logo--vertical.svg') no-repeat right 120px; From 5d2cf9623bbcb38d1bca66fdd02e8b8f5483a20d Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 14 Feb 2023 12:15:54 +0700 Subject: [PATCH 060/108] fix: adding country code before validate the phone number --- src/libs/OptionsListUtils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 9fb53c161d9a..b78cf1e154cf 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -575,6 +575,9 @@ function getOptions(reports, personalDetails, { let userToInvite = null; const noOptions = (recentReportOptions.length + personalDetailsOptions.length) === 0; const noOptionsMatchExactly = !_.find(personalDetailsOptions.concat(recentReportOptions), option => option.login === searchValue.toLowerCase()); + + // If the phone number doesn't have an international code then let's prefix it with the + // current user's international code based on their IP address. const login = (Str.isValidPhone(searchValue) && !searchValue.includes('+')) ? `+${countryCodeByIP}${searchValue}` : searchValue; @@ -585,9 +588,6 @@ function getOptions(reports, personalDetails, { && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase())) && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) ) { - // If the phone number doesn't have an international code then let's prefix it with the - // current user's international code based on their IP address. - userToInvite = createOption([login], personalDetails, null, reportActions, { showChatPreviewLine, }); From 62de4de3b5ffd7a164a34fe895b128b33fa5bba8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 14 Feb 2023 12:24:25 +0700 Subject: [PATCH 061/108] fix: adding country code before validate --- src/libs/OptionsListUtils.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index b78cf1e154cf..61de076d9382 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -581,12 +581,12 @@ function getOptions(reports, personalDetails, { const login = (Str.isValidPhone(searchValue) && !searchValue.includes('+')) ? `+${countryCodeByIP}${searchValue}` : searchValue; - if (searchValue && (noOptions || noOptionsMatchExactly) - && !isCurrentUser({login: searchValue}) - && _.every(selectedOptions, option => option.login !== searchValue) - && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue)) || Str.isValidPhone(login)) - && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase())) - && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) + if (login && (noOptions || noOptionsMatchExactly) + && !isCurrentUser({login}) + && _.every(selectedOptions, option => option.login !== login) + && ((Str.isValidEmail(login) && !Str.isDomainEmail(login)) || Str.isValidPhone(login)) + && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(login).toLowerCase())) + && (login !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) ) { userToInvite = createOption([login], personalDetails, null, reportActions, { showChatPreviewLine, From e1846f3383c3aabe78f1246960836de13a5ffe15 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Tue, 14 Feb 2023 09:22:38 -0800 Subject: [PATCH 062/108] Use existing color for supporting text, change legal links to blue --- docs/_sass/_colors.scss | 1 - docs/_sass/_main.scss | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_sass/_colors.scss b/docs/_sass/_colors.scss index e520cb9f018c..baf4df4ddef0 100644 --- a/docs/_sass/_colors.scss +++ b/docs/_sass/_colors.scss @@ -12,7 +12,6 @@ $color-dark-green: #1B5744; $color-darker-green: #002E22; $color-super-dark-green: #061B09; $color-light-blue: #8DC8FF; -$color-text-supporting: #7D8B8F; $color-link: #5AB0FF; $color-link-hovered: #B0D9FF; diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 18981db26a5d..28c1e00850b2 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -641,6 +641,7 @@ button { } &__social-icons { + padding-bottom: 20px; a { color: $color-white; @@ -653,11 +654,11 @@ button { } &__fine-print { - color: $color-text-supporting; + color: $color-gray-green; font-size: 10px; a { - color: $color-green; + color: $color-link; } } From ab17ffec06923d1dcd599da0ebb4fdcd76ade30a Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Tue, 14 Feb 2023 09:38:06 -0800 Subject: [PATCH 063/108] Remove unused variables, align footer with content --- docs/_sass/_main.scss | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 28c1e00850b2..93d53b22ccf4 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -2,10 +2,6 @@ @import 'colors'; @import 'fonts'; -$sidepaneWidthMobile : 500px; -$smallSpacing : 15px; -$mediumSpacing : 32px; - * { margin: 0; padding: 0; @@ -678,11 +674,10 @@ button { &__wrapper { margin: 0 auto; max-width: 1000px; - padding: 64px $mediumSpacing 0; + padding: 64px 0 0; @include maxBreakpoint($breakpoint-tablet) { - max-width: $sidepaneWidthMobile; - padding: 64px 40px 20px; + padding: 64px 0px 20px; } } From 65da03ed256f0d41dbb288f3f42ac5e9a70b9c27 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Tue, 14 Feb 2023 09:42:22 -0800 Subject: [PATCH 064/108] Use only copyright as there is no login on this page --- docs/_includes/footer.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 5c603e7d5c62..bbbf9fb3117d 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -111,9 +111,6 @@

Get Started

- - -
From b8f16397e824667428ae0d27552c2a3b08278ae9 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 14 Feb 2023 14:25:50 -0500 Subject: [PATCH 065/108] Update Your-Expensify-Partner-Manager.md Removed the space so that the link shows correctly! --- docs/articles/other/Your-Expensify-Partner-Manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/other/Your-Expensify-Partner-Manager.md b/docs/articles/other/Your-Expensify-Partner-Manager.md index a208d83a72de..9a68fbfd8b39 100644 --- a/docs/articles/other/Your-Expensify-Partner-Manager.md +++ b/docs/articles/other/Your-Expensify-Partner-Manager.md @@ -12,7 +12,7 @@ Unlike Concierge, a Partner Manager’s support will not be real-time, 24 hours For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. # How do I know if I have a Partner Manager? -For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University] (https://use.expensify.com/accountants) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit. +For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University](https://use.expensify.com/accountants) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit. You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. From 27f4cc9f1ab1ee0e97bd287b58d8ff74a73bff19 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Tue, 14 Feb 2023 21:35:12 +0200 Subject: [PATCH 066/108] Save actions from push payload in Onyx. --- src/libs/Notification/PushNotification/index.native.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 4dc3ad1c3a8e..c24fbc00f3b3 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -57,13 +57,6 @@ function pushNotificationEventCallback(eventType, notification) { function init() { // Setup event listeners UrbanAirship.addListener(EventType.PushReceived, (notification) => { - // If a push notification is received while the app is in foreground, - // we'll assume pusher is connected so we'll ignore it and not fetch the same data twice. - if (AppState.currentState === 'active') { - Log.info('[PUSH_NOTIFICATION] Push received while app is in foreground, not executing any callback.'); - return; - } - pushNotificationEventCallback(EventType.PushReceived, notification); }); From 4f26429dc2928341f7ea7da7dac9c869ecb8c6b9 Mon Sep 17 00:00:00 2001 From: Cristi Paval Date: Tue, 14 Feb 2023 23:05:54 +0200 Subject: [PATCH 067/108] Fix Lint error. --- src/libs/Notification/PushNotification/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index c24fbc00f3b3..006ed4b06a5e 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import {AppState} from 'react-native'; import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native'; import lodashGet from 'lodash/get'; import Log from '../../Log'; From 78d7d842d5fe6111fea98df8b45f5cb78612a64d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Feb 2023 23:55:31 -0800 Subject: [PATCH 068/108] move available iou options logic to util --- src/libs/ReportUtils.js | 22 ++++++++ src/pages/home/report/ReportActionCompose.js | 53 +++++++------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e29a4f6414ad..f659fc1b2c38 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1,6 +1,7 @@ import _ from 'underscore'; import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; +import lodashIntersection from 'lodash/intersection'; import Onyx from 'react-native-onyx'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import {InteractionManager} from 'react-native'; @@ -1475,6 +1476,26 @@ function openReportFromDeepLink(url) { }); } +function getIOUOptions(report, reportParticipants, betas) { + const participants = _.filter(reportParticipants, email => currentUserPersonalDetails.login !== email); + const hasExcludedIOUEmails = lodashIntersection(reportParticipants, CONST.EXPENSIFY_EMAILS).length > 0; + const hasMultipleParticipants = participants.length > 1; + + if (hasExcludedIOUEmails || participants.length === 0 || !Permissions.canUseIOU(betas)) { + return []; + } + + // User created policy rooms and default rooms like #admins or #announce will always have the Split Bill option + // unless there are no participants at all (e.g. #admins room for a policy with only 1 admin) + // DM chats and workspace chats will have the Split Bill option only when there are at least 3 people in the chat. + if (isChatRoom(report) || hasMultipleParticipants) { + return [CONST.IOU.IOU_TYPE.SPLIT]; + } + + // DM chats and workspace chats that only have 2 people will see the Send / Request money options. + return [CONST.IOU.IOU_TYPE.REQUEST, ...(Permissions.canUseIOUSend(betas) ? [CONST.IOU.IOU_TYPE.SEND] : [])]; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -1532,4 +1553,5 @@ export { canSeeDefaultRoom, getCommentLength, openReportFromDeepLink, + getIOUOptions, }; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 4075e50b3b37..a94c967abc4c 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -273,44 +273,25 @@ class ReportActionCompose extends React.Component { * @returns {Array} */ getIOUOptions(reportParticipants) { - const participants = _.filter(reportParticipants, email => this.props.currentUserPersonalDetails.login !== email); - const hasExcludedIOUEmails = lodashIntersection(reportParticipants, CONST.EXPENSIFY_EMAILS).length > 0; - const hasMultipleParticipants = participants.length > 1; - - if (hasExcludedIOUEmails || participants.length === 0 || !Permissions.canUseIOU(this.props.betas)) { - return []; - } - - // User created policy rooms and default rooms like #admins or #announce will always have the Split Bill option - // unless there are no participants at all (e.g. #admins room for a policy with only 1 admin) - // DM chats and workspace chats will have the Split Bill option only when there are at least 3 people in the chat. - if (ReportUtils.isChatRoom(this.props.report) || hasMultipleParticipants) { - return [ - { - icon: Expensicons.Receipt, - text: this.props.translate('iou.splitBill'), - onSelected: () => Navigation.navigate(ROUTES.getIouSplitRoute(this.props.reportID)), + const options = { + [CONST.IOU.IOU_TYPE.SPLIT]: { + icon: Expensicons.Receipt, + text: this.props.translate('iou.splitBill'), + onSelected: () => Navigation.navigate(ROUTES.getIouSplitRoute(this.props.reportID)), + }, + [CONST.IOU.IOU_TYPE.REQUEST]: { + icon: Expensicons.MoneyCircle, + text: this.props.translate('iou.requestMoney'), + onSelected: () => Navigation.navigate(ROUTES.getIouRequestRoute(this.props.reportID)), }, - ]; - } - - // DM chats that only have 2 people will see the Send / Request money options. - // Workspace chats should only see the Request money option, as "easy overages" is not available. - return [ - { - icon: Expensicons.MoneyCircle, - text: this.props.translate('iou.requestMoney'), - onSelected: () => Navigation.navigate(ROUTES.getIouRequestRoute(this.props.reportID)), + [CONST.IOU.IOU_TYPE.SEND]: { + icon: Expensicons.Send, + text: this.props.translate('iou.sendMoney'), + onSelected: () => Navigation.navigate(ROUTES.getIOUSendRoute(this.props.reportID)), }, - ...((Permissions.canUseIOUSend(this.props.betas) && !ReportUtils.isPolicyExpenseChat(this.props.report)) - ? [ - { - icon: Expensicons.Send, - text: this.props.translate('iou.sendMoney'), - onSelected: () => Navigation.navigate(ROUTES.getIOUSendRoute(this.props.reportID)), - }] - : []), - ]; + }; + return ReportUtils.getIOUOptions(this.props.report, reportParticipants, this.props.betas) + .map(option => options[option]); } /** From 1c06eccfdb4a0821a918abc21e94d2d01b5d59e3 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Feb 2023 23:57:13 -0800 Subject: [PATCH 069/108] show text based on iou options --- src/components/ReportWelcomeText.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js index c732cc589d27..7caac4156df4 100644 --- a/src/components/ReportWelcomeText.js +++ b/src/components/ReportWelcomeText.js @@ -15,6 +15,7 @@ import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import Tooltip from './Tooltip'; import reportPropTypes from '../pages/reportPropTypes'; +import CONST from '../CONST'; const personalDetailsPropTypes = PropTypes.shape({ /** The login of the person (either email or phone number) */ @@ -62,6 +63,7 @@ const ReportWelcomeText = (props) => { isMultipleParticipant, ); const roomWelcomeMessage = ReportUtils.getRoomWelcomeMessage(props.report, props.policies); + const iouOptions = ReportUtils.getIOUOptions(props.report, participants, props.betas); return ( <> @@ -123,10 +125,12 @@ const ReportWelcomeText = (props) => { {(index < displayNamesWithTooltips.length - 2) && , } ))} - - {/* Need to confirm copy for the below with marketing, and then add to translations. */} - {props.translate('reportActionsView.usePlusButton')} - + {(iouOptions.includes(CONST.IOU.IOU_TYPE.SEND) || iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)) && ( + + {/* Need to confirm copy for the below with marketing, and then add to translations. */} + {props.translate('reportActionsView.usePlusButton')} + + )} )} @@ -141,6 +145,9 @@ ReportWelcomeText.displayName = 'ReportWelcomeText'; export default compose( withLocalize, withOnyx({ + betas: { + key: ONYXKEYS.BETAS, + }, personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS, }, From 57bf5ec1d674c5c9fd234068512ac422d7336cb5 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 Feb 2023 01:16:24 -0800 Subject: [PATCH 070/108] add test --- src/libs/__mocks__/Permissions.js | 2 + tests/unit/ReportUtilsTest.js | 76 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/libs/__mocks__/Permissions.js b/src/libs/__mocks__/Permissions.js index 50d093571d92..8cf166b384e6 100644 --- a/src/libs/__mocks__/Permissions.js +++ b/src/libs/__mocks__/Permissions.js @@ -13,4 +13,6 @@ export default { canUseDefaultRooms: betas => _.contains(betas, CONST.BETAS.DEFAULT_ROOMS), canUsePolicyRooms: betas => _.contains(betas, CONST.BETAS.POLICY_ROOMS), canUsePolicyExpenseChat: betas => _.contains(betas, CONST.BETAS.POLICY_EXPENSE_CHAT), + canUseIOU: betas => _.contains(betas, CONST.BETAS.IOU), + canUseIOUSend: betas => _.contains(betas, CONST.BETAS.IOU_SEND), }; diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index dc74b7c85b23..343380cbf224 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -1,10 +1,14 @@ import Onyx from 'react-native-onyx'; +import _ from 'underscore'; import CONST from '../../src/CONST'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as ReportUtils from '../../src/libs/ReportUtils'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import * as LHNTestUtils from '../utils/LHNTestUtils'; +// Be sure to include the mocked permissions library or else the beta tests won't work +jest.mock('../../src/libs/Permissions'); + const currentUserEmail = 'bjorn@vikings.net'; const participantsPersonalDetails = { 'ragnar@vikings.net': { @@ -303,4 +307,76 @@ describe('ReportUtils', () => { expect(ReportUtils.hasOutstandingIOU(report, 'b@b.com', iouReports)).toBe(false); }); }); + + describe('getIOUOptions', () => { + const participants = _.keys(participantsPersonalDetails); + + beforeAll(() => { + Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, { + [currentUserEmail]: { + login: currentUserEmail, + }, + }); + }); + + afterAll(() => Onyx.clear()); + + describe('return empty iou options if', () => { + it('participants contains excluded iou emails', () => { + const allEmpty = _.every(CONST.EXPENSIFY_EMAILS, (email) => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, email], [CONST.BETAS.IOU]); + return iouOptions.length === 0; + }); + expect(allEmpty).toBe(true); + }); + + it('no participants except self', () => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail], [CONST.BETAS.IOU]); + expect(iouOptions.length).toBe(0); + }); + + it('no iou permission', () => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, participants], []); + expect(iouOptions.length).toBe(0); + }); + }); + + describe('return only iou split option if', () => { + it('a chat room', () => { + const onlyHaveSplitOption = _.every([ + CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, + CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, + CONST.REPORT.CHAT_TYPE.DOMAIN_ALL, + CONST.REPORT.CHAT_TYPE.POLICY_ROOM, + ], (chatType) => { + const report = { + ...LHNTestUtils.getFakeReport(), + chatType, + }; + const iouOptions = ReportUtils.getIOUOptions(report, [currentUserEmail, participants[0]], [CONST.BETAS.IOU]); + return iouOptions.length === 1 && iouOptions.includes(CONST.IOU.IOU_TYPE.SPLIT); + }); + expect(onlyHaveSplitOption).toBe(true); + }); + + it('has multiple participants exclude self', () => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, ...participants], [CONST.BETAS.IOU]); + expect(iouOptions.length).toBe(1); + expect(iouOptions.includes(CONST.IOU.IOU_TYPE.SPLIT)).toBe(true); + }); + }); + + it('return only iou request option if does not have iou send permission', () => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, participants], [CONST.BETAS.IOU]); + expect(iouOptions.length).toBe(1); + expect(iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)).toBe(true); + }); + + it('return both iou send and request money', () => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, participants], [CONST.BETAS.IOU, CONST.BETAS.IOU_SEND]); + expect(iouOptions.length).toBe(2); + expect(iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)).toBe(true); + expect(iouOptions.includes(CONST.IOU.IOU_TYPE.SEND)).toBe(true); + }); + }); }); From 1fdfabd66baae052ea3cc6a8b19eede8d7494bfa Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 Feb 2023 01:16:34 -0800 Subject: [PATCH 071/108] fix lint --- src/pages/home/report/ReportActionCompose.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index a94c967abc4c..0efb256e8144 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -8,7 +8,6 @@ import { import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; -import lodashIntersection from 'lodash/intersection'; import styles from '../../../styles/styles'; import themeColors from '../../../styles/themes/default'; import Composer from '../../../components/Composer'; @@ -23,7 +22,6 @@ import compose from '../../../libs/compose'; import PopoverMenu from '../../../components/PopoverMenu'; import willBlurTextInputOnTapOutside from '../../../libs/willBlurTextInputOnTapOutside'; import CONST from '../../../CONST'; -import Permissions from '../../../libs/Permissions'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import reportActionPropTypes from './reportActionPropTypes'; @@ -280,18 +278,17 @@ class ReportActionCompose extends React.Component { onSelected: () => Navigation.navigate(ROUTES.getIouSplitRoute(this.props.reportID)), }, [CONST.IOU.IOU_TYPE.REQUEST]: { - icon: Expensicons.MoneyCircle, - text: this.props.translate('iou.requestMoney'), - onSelected: () => Navigation.navigate(ROUTES.getIouRequestRoute(this.props.reportID)), - }, + icon: Expensicons.MoneyCircle, + text: this.props.translate('iou.requestMoney'), + onSelected: () => Navigation.navigate(ROUTES.getIouRequestRoute(this.props.reportID)), + }, [CONST.IOU.IOU_TYPE.SEND]: { icon: Expensicons.Send, text: this.props.translate('iou.sendMoney'), onSelected: () => Navigation.navigate(ROUTES.getIOUSendRoute(this.props.reportID)), }, }; - return ReportUtils.getIOUOptions(this.props.report, reportParticipants, this.props.betas) - .map(option => options[option]); + return _.map(ReportUtils.getIOUOptions(this.props.report, reportParticipants, this.props.betas), option => options[option]); } /** From 636e4286fbe817642e7294f79c373e62089d2688 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 Feb 2023 01:21:51 -0800 Subject: [PATCH 072/108] add comment --- src/libs/ReportUtils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f659fc1b2c38..2bcd9a057cec 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1476,6 +1476,12 @@ function openReportFromDeepLink(url) { }); } +/** + * @param {Object} report + * @param {Array} reportParticipants + * @param {Array} betas + * @returns {Array} + */ function getIOUOptions(report, reportParticipants, betas) { const participants = _.filter(reportParticipants, email => currentUserPersonalDetails.login !== email); const hasExcludedIOUEmails = lodashIntersection(reportParticipants, CONST.EXPENSIFY_EMAILS).length > 0; From fa8ff08e9e9f93f8e186b385cf693c8e482fee56 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 Feb 2023 01:30:19 -0800 Subject: [PATCH 073/108] fix lint again --- src/libs/ReportUtils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2bcd9a057cec..c8d8d9d18b77 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1477,9 +1477,9 @@ function openReportFromDeepLink(url) { } /** - * @param {Object} report - * @param {Array} reportParticipants - * @param {Array} betas + * @param {Object} report + * @param {Array} reportParticipants + * @param {Array} betas * @returns {Array} */ function getIOUOptions(report, reportParticipants, betas) { From e8103c4a883e575dccaea11c84cdb276176239e8 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:46:57 +0100 Subject: [PATCH 074/108] Do not capture events targeting textarea nodes --- src/components/ArrowKeyFocusManager.js | 4 ++-- src/libs/KeyboardShortcut/index.js | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/ArrowKeyFocusManager.js b/src/components/ArrowKeyFocusManager.js index 84283915aba1..8fd65ef95f7f 100644 --- a/src/components/ArrowKeyFocusManager.js +++ b/src/components/ArrowKeyFocusManager.js @@ -48,7 +48,7 @@ class ArrowKeyFocusManager extends Component { } this.props.onFocusedIndexChanged(newFocusedIndex); - }, arrowUpConfig.descriptionKey, arrowUpConfig.modifiers, true); + }, arrowUpConfig.descriptionKey, arrowUpConfig.modifiers, true, false, 0, true, ['TEXTAREA']); this.unsubscribeArrowDownKey = KeyboardShortcut.subscribe(arrowDownConfig.shortcutKey, () => { if (this.props.maxIndex < 0) { @@ -66,7 +66,7 @@ class ArrowKeyFocusManager extends Component { } this.props.onFocusedIndexChanged(newFocusedIndex); - }, arrowDownConfig.descriptionKey, arrowDownConfig.modifiers, true); + }, arrowDownConfig.descriptionKey, arrowDownConfig.modifiers, true, false, 0, true, ['TEXTAREA']); } componentWillUnmount() { diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js index f948d6991461..1b6faa948d17 100644 --- a/src/libs/KeyboardShortcut/index.js +++ b/src/libs/KeyboardShortcut/index.js @@ -75,6 +75,11 @@ function bindHandlerToKeydownEvent(event) { // Loop over all the callbacks _.every(eventHandlers[displayName], (callback) => { + // Early return for excludeNodes + if (_.contains(callback.excludeNodes, event.target.nodeName)) { + return true; + } + // If configured to do so, prevent input text control to trigger this event if (!callback.captureOnInputs && ( event.target.nodeName === 'INPUT' @@ -145,9 +150,10 @@ function getPlatformEquivalentForKeys(keys) { * @param {Boolean|Function} [shouldBubble] Should the event bubble? * @param {Number} [priority] The position the callback should take in the stack. 0 means top priority, and 1 means less priority than the most recently added. * @param {Boolean} [shouldPreventDefault] Should call event.preventDefault after callback? + * @param {Array} [excludeNodes] Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) * @returns {Function} clean up method */ -function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true) { +function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, excludeNodes = []) { const platformAdjustedModifiers = getPlatformEquivalentForKeys(modifiers); const displayName = getDisplayName(key, platformAdjustedModifiers); if (!_.has(eventHandlers, displayName)) { @@ -161,6 +167,7 @@ function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOn captureOnInputs, shouldPreventDefault, shouldBubble, + excludeNodes, }); if (descriptionKey) { From 96c20f6c267942d4ddc8622d02496e89ec75a397 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 15 Feb 2023 10:35:57 -0300 Subject: [PATCH 075/108] fix migration test --- tests/unit/MigrationTest.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index de50239ff32c..9ad4b6815cdd 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -97,13 +97,13 @@ describe('Migrations', () => { }); describe('AddLastVisibleActionCreated', () => { - it('Should add lastVisibleActionCreated wherever lastMessageTimestamp currently is', () => ( + it('Should add lastVisibleActionCreated wherever lastActionCreated currently is', () => ( Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT}1`]: { - lastMessageTimestamp: 1668562273702, + lastActionCreated: '2022-11-16 01:31:13.702', }, [`${ONYXKEYS.COLLECTION.REPORT}2`]: { - lastMessageTimestamp: 1668562314821, + lastActionCreated: '2022-11-16 01:31:54.821', }, }) .then(AddLastVisibleActionCreated) From 95d9f3b4b6e7af6576e87b409dfed22f6bd257ff Mon Sep 17 00:00:00 2001 From: daraksha Date: Wed, 15 Feb 2023 21:46:11 +0530 Subject: [PATCH 076/108] add userSelectNone style for children --- src/components/OfflineWithFeedback.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index e80618ce0a47..64ce8b62170e 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -72,7 +72,7 @@ function applyStrikeThrough(children) { if (!React.isValidElement(child)) { return child; } - const props = {style: StyleUtils.combineStyles(child.props.style, styles.offlineFeedback.deleted)}; + const props = {style: StyleUtils.combineStyles(child.props.style, styles.offlineFeedback.deleted, styles.userSelectNone)}; if (child.props.children) { props.children = applyStrikeThrough(child.props.children); } From 8c94c3fd1a11bba25bbc3ff46439d3b45c1218b5 Mon Sep 17 00:00:00 2001 From: daraksha Date: Wed, 15 Feb 2023 21:47:31 +0530 Subject: [PATCH 077/108] disable payment method when pending action is delete --- src/pages/settings/Payments/PaymentMethodList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Payments/PaymentMethodList.js b/src/pages/settings/Payments/PaymentMethodList.js index 8074bf703563..e4372df032b4 100644 --- a/src/pages/settings/Payments/PaymentMethodList.js +++ b/src/pages/settings/Payments/PaymentMethodList.js @@ -137,6 +137,7 @@ class PaymentMethodList extends Component { onPress: e => this.props.onPress(e, paymentMethod.accountType, paymentMethod.accountData, paymentMethod.isDefault, paymentMethod.methodID), iconFill: this.isPaymentMethodActive(paymentMethod) ? StyleUtils.getIconFillColor(CONST.BUTTON_STATES.PRESSED) : null, wrapperStyle: this.isPaymentMethodActive(paymentMethod) ? [StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)] : null, + disabled: paymentMethod.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, })); return combinedPaymentMethods; From 8f7e708fe5cea4bd8da3011a3a56f2859ba574bc Mon Sep 17 00:00:00 2001 From: Prince Mendiratta Date: Wed, 15 Feb 2023 23:09:11 +0530 Subject: [PATCH 078/108] [Onfido] locale specific language Signed-off-by: Prince Mendiratta --- src/components/Onfido/BaseOnfidoWeb.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.js index 9df26bf5f62f..c3e58bc674b0 100644 --- a/src/components/Onfido/BaseOnfidoWeb.js +++ b/src/components/Onfido/BaseOnfidoWeb.js @@ -107,6 +107,7 @@ class Onfido extends React.Component { onModalRequestClose: () => { Log.hmmm('Onfido user closed the modal'); }, + language: this.props.preferredLocale, }); window.addEventListener('userAnalyticsEvent', (event) => { From 13f07f7e594690a653b20782a2197d5aca84c9ba Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 15 Feb 2023 09:38:04 -1000 Subject: [PATCH 079/108] Use correct lastMessageText for attachments --- src/CONST.js | 1 + src/libs/ReportActionsUtils.js | 5 ++++- src/libs/ReportUtils.js | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 10f2a7e63dec..393fb03544e8 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -518,6 +518,7 @@ const CONST = { URL: 'url', }, + ATTACHMENT_MESSAGE_TEXT: '[Attachment]', ATTACHMENT_SOURCE_ATTRIBUTE: 'data-expensify-source', ATTACHMENT_PREVIEW_ATTRIBUTE: 'src', ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE: 'data-name', diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index f260bbc4c58d..ae92d062799b 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -165,8 +165,11 @@ function getLastVisibleAction(reportID, actionsToMerge = {}) { */ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const lastVisibleAction = getLastVisibleAction(reportID, actionsToMerge); - const htmlText = lodashGet(lastVisibleAction, 'message[0].html', ''); + if (lastVisibleAction.isAttachment) { + return CONST.ATTACHMENT_MESSAGE_TEXT; + } + const htmlText = lodashGet(lastVisibleAction, 'message[0].html', ''); const parser = new ExpensiMark(); const messageText = parser.htmlToText(htmlText); return String(messageText) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0a4fdcdebf87..28306b8061e2 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -107,7 +107,7 @@ function isIOUReport(report) { * @returns {Boolean} */ function isReportMessageAttachment({text, html}) { - return text === '[Attachment]' && html !== '[Attachment]'; + return text === CONST.ATTACHMENT_MESSAGE_TEXT && html !== CONST.ATTACHMENT_MESSAGE_TEXT; } /** @@ -791,7 +791,7 @@ function buildOptimisticAddCommentReportAction(text, file) { const htmlForNewComment = isAttachment ? 'Uploading Attachment...' : commentText; // Remove HTML from text when applying optimistic offline comment - const textForNewComment = isAttachment ? '[Attachment]' + const textForNewComment = isAttachment ? CONST.ATTACHMENT_MESSAGE_TEXT : parser.htmlToText(htmlForNewComment); return { From 694b07484d32a6025494e727831398434016d501 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 15 Feb 2023 10:35:54 -1000 Subject: [PATCH 080/108] fix dependency cycle --- src/libs/ReportActionsUtils.js | 4 +++- src/libs/ReportUtils.js | 12 +----------- src/libs/isReportMessageAttachment.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 src/libs/isReportMessageAttachment.js diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index ae92d062799b..deac1d9c788a 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -7,6 +7,7 @@ import moment from 'moment'; import * as CollectionUtils from './CollectionUtils'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; +import isReportMessageAttachment from './isReportMessageAttachment'; const allReportActions = {}; Onyx.connect({ @@ -165,7 +166,8 @@ function getLastVisibleAction(reportID, actionsToMerge = {}) { */ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const lastVisibleAction = getLastVisibleAction(reportID, actionsToMerge); - if (lastVisibleAction.isAttachment) { + const message = lodashGet(lastVisibleAction, ['message', 0]); + if (isReportMessageAttachment(message)) { return CONST.ATTACHMENT_MESSAGE_TEXT; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 28306b8061e2..0ef58191de4d 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -19,6 +19,7 @@ import Permissions from './Permissions'; import DateUtils from './DateUtils'; import linkingConfig from './Navigation/linkingConfig'; import * as defaultAvatars from '../components/Icon/DefaultAvatars'; +import isReportMessageAttachment from './isReportMessageAttachment'; let sessionEmail; Onyx.connect({ @@ -99,17 +100,6 @@ function isIOUReport(report) { return report && _.has(report, 'total'); } -/** - * Check whether a report action is Attachment or not. - * Ignore messages containing [Attachment] as the main content. Attachments are actions with only text as [Attachment]. - * - * @param {Object} reportActionMessage report action's message as text and html - * @returns {Boolean} - */ -function isReportMessageAttachment({text, html}) { - return text === CONST.ATTACHMENT_MESSAGE_TEXT && html !== CONST.ATTACHMENT_MESSAGE_TEXT; -} - /** * Given a collection of reports returns them sorted by last read * diff --git a/src/libs/isReportMessageAttachment.js b/src/libs/isReportMessageAttachment.js new file mode 100644 index 000000000000..e524d5f79f64 --- /dev/null +++ b/src/libs/isReportMessageAttachment.js @@ -0,0 +1,12 @@ +import CONST from '../CONST'; + +/** + * Check whether a report action is Attachment or not. + * Ignore messages containing [Attachment] as the main content. Attachments are actions with only text as [Attachment]. + * + * @param {Object} reportActionMessage report action's message as text and html + * @returns {Boolean} + */ +export default function isReportMessageAttachment({text, html}) { + return text === CONST.ATTACHMENT_MESSAGE_TEXT && html !== CONST.ATTACHMENT_MESSAGE_TEXT; +} From aad831981abfd17dcfcfb553389deb3b062fee51 Mon Sep 17 00:00:00 2001 From: daraksha Date: Thu, 16 Feb 2023 02:36:08 +0530 Subject: [PATCH 081/108] Use shouldDelayFocus --- src/components/RoomNameInput/index.native.js | 1 + src/components/RoomNameInput/roomNameInputPropTypes.js | 4 ++++ src/pages/workspace/WorkspaceNewRoomPage.js | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index a2af6fbbf049..f261b76bafcc 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -46,6 +46,7 @@ class RoomNameInput extends Component { onBlur={this.props.onBlur} autoFocus={this.props.autoFocus} autoCapitalize="none" + shouldDelayFocus={this.props.shouldDelayFocus} /> ); } diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 53666a6ae694..2f4af1ae2e92 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -27,6 +27,9 @@ const propTypes = { /** AutoFocus */ autoFocus: PropTypes.bool, + + /** Whether we should wait before focusing the TextInput, useful when using transitions on Android */ + shouldDelayFocus: PropTypes.bool, }; const defaultProps = { @@ -39,6 +42,7 @@ const defaultProps = { inputID: undefined, onBlur: () => {}, autoFocus: false, + shouldDelayFocus: false, }; export {propTypes, defaultProps}; diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index f524e34b48c7..0cc96e7ce33a 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -19,6 +19,7 @@ import Permissions from '../../libs/Permissions'; import Log from '../../libs/Log'; import * as ValidationUtils from '../../libs/ValidationUtils'; import Form from '../../components/Form'; +import shouldDelayFocus from '../../libs/shouldDelayFocus'; const propTypes = { /** All reports shared with the user */ @@ -141,6 +142,7 @@ class WorkspaceNewRoomPage extends React.Component { From 50528cae6cc920fbcd32af7c5317c7468c1418fa Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 15 Feb 2023 11:56:18 -1000 Subject: [PATCH 082/108] Fix avatar preview size --- src/libs/ReportUtils.js | 13 +++++++++++++ src/pages/DetailsPage.js | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0a4fdcdebf87..fb07dd3f163a 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -524,6 +524,18 @@ function getAvatar(avatarURL, login) { return avatarURL; } +/** + * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. + * This removes that part of the URL so the full version of the image can load. + * + * @param {String} [avatarURL] + * @param {String} [login] + * @returns {String} + */ +function getFullSizeAvatar(avatarURL, login) { + return getAvatar(avatarURL, login).replace('_128', ''); +} + /** * Returns the appropriate icons for the given chat report using the stored personalDetails. * The Avatar sources can be URLs or Icon components according to the chat type. @@ -1546,4 +1558,5 @@ export { canSeeDefaultRoom, getCommentLength, openReportFromDeepLink, + getFullSizeAvatar, }; diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 447cb7a244b9..dcc6d74270b0 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -125,7 +125,7 @@ class DetailsPage extends React.PureComponent { {({show}) => ( From 30b22972e0c46f04af47163b065d825fc03f21e5 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 15 Feb 2023 12:03:33 -1000 Subject: [PATCH 083/108] support the svg avatars --- src/libs/ReportUtils.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index fb07dd3f163a..c73d51b65e35 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -515,7 +515,7 @@ function isDefaultAvatar(avatarURL) { * * @param {String} [avatarURL] - the avatar source from user's personalDetails * @param {String} [login] - the email of the user - * @returns {String | Function} + * @returns {String|Function} */ function getAvatar(avatarURL, login) { if (isDefaultAvatar(avatarURL)) { @@ -530,10 +530,14 @@ function getAvatar(avatarURL, login) { * * @param {String} [avatarURL] * @param {String} [login] - * @returns {String} + * @returns {String|Function} */ function getFullSizeAvatar(avatarURL, login) { - return getAvatar(avatarURL, login).replace('_128', ''); + const source = getAvatar(avatarURL, login); + if (!_.isString(source)) { + return source; + } + return source.replace('_128', ''); } /** From ffcec98724bfc517ce09fec9d22a955681f79982 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 15 Feb 2023 23:07:24 +0100 Subject: [PATCH 084/108] [Fix] Add missing Emoji.types --- src/libs/EmojiTrie.js | 8 ++++---- src/libs/EmojiUtils.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/EmojiTrie.js b/src/libs/EmojiTrie.js index 5d7a15dbf885..5cf0f2a27b6c 100644 --- a/src/libs/EmojiTrie.js +++ b/src/libs/EmojiTrie.js @@ -12,19 +12,19 @@ for (let i = 0; i < emojis.length; i++) { if (emojis[i].name) { const node = emojisTrie.search(emojis[i].name); if (!node) { - emojisTrie.add(emojis[i].name, {code: emojis[i].code, suggestions: []}); + emojisTrie.add(emojis[i].name, {code: emojis[i].code, types: emojis[i].types, suggestions: []}); } else { - emojisTrie.update(emojis[i].name, {code: emojis[i].code, suggestions: node.metaData.suggestions}); + emojisTrie.update(emojis[i].name, {code: emojis[i].code, types: emojis[i].types, suggestions: node.metaData.suggestions}); } if (emojis[i].keywords) { for (let j = 0; j < emojis[i].keywords.length; j++) { const keywordNode = emojisTrie.search(emojis[i].keywords[j]); if (!keywordNode) { - emojisTrie.add(emojis[i].keywords[j], {suggestions: [{code: emojis[i].code, name: emojis[i].name}]}); + emojisTrie.add(emojis[i].keywords[j], {suggestions: [{code: emojis[i].code, types: emojis[i].types, name: emojis[i].name}]}); } else { emojisTrie.update(emojis[i].keywords[j], - {...keywordNode.metaData, suggestions: [...keywordNode.metaData.suggestions, {code: emojis[i].code, name: emojis[i].name}]}); + {...keywordNode.metaData, suggestions: [...keywordNode.metaData.suggestions, {code: emojis[i].code, types: emojis[i].types, name: emojis[i].name}]}); } } } diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index 6f3583b70c0a..dc07e58e846b 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -228,7 +228,7 @@ function suggestEmojis(text, limit = 5) { if (matching.length === limit) { return matching; } - matching.push({code: nodes[j].metaData.code, name: nodes[j].name}); + matching.push({code: nodes[j].metaData.code, name: nodes[j].name,types: nodes[j].metaData.types}); } const suggestions = nodes[j].metaData.suggestions; for (let i = 0; i < suggestions.length; i++) { From f8aade467c690d7a4f663722a834b0dcb465b474 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Thu, 16 Feb 2023 00:46:33 +0100 Subject: [PATCH 085/108] Fix lint --- src/libs/EmojiUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index dc07e58e846b..bee46547a3e8 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -228,7 +228,7 @@ function suggestEmojis(text, limit = 5) { if (matching.length === limit) { return matching; } - matching.push({code: nodes[j].metaData.code, name: nodes[j].name,types: nodes[j].metaData.types}); + matching.push({code: nodes[j].metaData.code, name: nodes[j].name, types: nodes[j].metaData.types}); } const suggestions = nodes[j].metaData.suggestions; for (let i = 0; i < suggestions.length; i++) { From f45d3b060ed8fe4bcbbdac85bb3da82ff210df45 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Thu, 16 Feb 2023 00:59:47 +0100 Subject: [PATCH 086/108] [Fix] missing emojis.types in EmojiTest --- tests/unit/EmojiTest.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index 25626872caf1..224e3adafc05 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -126,6 +126,25 @@ describe('EmojiTest', () => { it('correct suggests emojis accounting for keywords', () => { const text = ':thumb'; - expect(EmojiUtils.suggestEmojis(text)).toEqual([{code: '👍', name: '+1'}, {code: '👎', name: '-1'}]); + expect(EmojiUtils.suggestEmojis(text)).toEqual([{ + code: '👍', + name: '+1', + types: ['👍🏿', + '👍🏾', + '👍🏽', + '👍🏼', + '👍🏻', + ], + }, { + code: '👎', + name: '-1', + types: [ + '👎🏿', + '👎🏾', + '👎🏽', + '👎🏼', + '👎🏻', + ], + }]); }); }); From 8152828ae4c13cfa3bd657ff520e756a593e9f53 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 Feb 2023 18:53:14 -0800 Subject: [PATCH 087/108] merge with main --- src/libs/ReportUtils.js | 8 ++++++-- tests/unit/ReportUtilsTest.js | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7844b9fcb882..9326137f0a73 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1512,8 +1512,12 @@ function getIOUOptions(report, reportParticipants, betas) { return [CONST.IOU.IOU_TYPE.SPLIT]; } - // DM chats and workspace chats that only have 2 people will see the Send / Request money options. - return [CONST.IOU.IOU_TYPE.REQUEST, ...(Permissions.canUseIOUSend(betas) ? [CONST.IOU.IOU_TYPE.SEND] : [])]; + // DM chats that only have 2 people will see the Send / Request money options. + // Workspace chats should only see the Request money option, as "easy overages" is not available. + return [ + CONST.IOU.IOU_TYPE.REQUEST, + ...(Permissions.canUseIOUSend(betas) && !isPolicyExpenseChat(report) ? [CONST.IOU.IOU_TYPE.SEND] : []), + ]; } export { diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 343380cbf224..87a7415ed02b 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -366,10 +366,22 @@ describe('ReportUtils', () => { }); }); - it('return only iou request option if does not have iou send permission', () => { - const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, participants], [CONST.BETAS.IOU]); - expect(iouOptions.length).toBe(1); - expect(iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)).toBe(true); + describe('return only iou request option if', () => { + it(' does not have iou send permission', () => { + const iouOptions = ReportUtils.getIOUOptions({}, [currentUserEmail, participants], [CONST.BETAS.IOU]); + expect(iouOptions.length).toBe(1); + expect(iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)).toBe(true); + }); + + it('a policy expense chat', () => { + const report = { + ...LHNTestUtils.getFakeReport(), + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + }; + const iouOptions = ReportUtils.getIOUOptions(report, [currentUserEmail, participants], [CONST.BETAS.IOU, CONST.BETAS.IOU_SEND]); + expect(iouOptions.length).toBe(1); + expect(iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)).toBe(true); + }); }); it('return both iou send and request money', () => { From 90c5fb227d736d399781c4f6a1696cd5b45786c0 Mon Sep 17 00:00:00 2001 From: "T.J" Date: Wed, 15 Feb 2023 20:56:24 -0800 Subject: [PATCH 088/108] Update routes.yml --- docs/_data/routes.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_data/routes.yml b/docs/_data/routes.yml index 4d1c135d577c..6f626fb5541f 100644 --- a/docs/_data/routes.yml +++ b/docs/_data/routes.yml @@ -44,5 +44,7 @@ hubs: articles: - href: Your-Expensify-Account-Manager title: Your Expensify Account Manager + - href: Your-Expensify-Partner-Manager + title: Your Expensify Partner Manager - href: Everything-About-Chat title: Everything About Chat From 84058e02f66c01ec0307fd37b7d9487f75af0a0f Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 Feb 2023 13:10:22 +0700 Subject: [PATCH 089/108] fix: toggle composer input only when the keyboard hide completely --- .../toggleReportActionComposeView/index.android.js | 14 ++++++++++++++ .../toggleReportActionComposeView/index.ios.js | 14 ++++++++++++++ .../home/report/ReportActionItemMessageEdit.js | 3 ++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/libs/toggleReportActionComposeView/index.android.js create mode 100644 src/libs/toggleReportActionComposeView/index.ios.js diff --git a/src/libs/toggleReportActionComposeView/index.android.js b/src/libs/toggleReportActionComposeView/index.android.js new file mode 100644 index 000000000000..cb5f3690fe6f --- /dev/null +++ b/src/libs/toggleReportActionComposeView/index.android.js @@ -0,0 +1,14 @@ +import {Keyboard} from 'react-native'; +import * as Composer from '../actions/Composer'; + +export default (shouldShowComposeInput, isSmallScreenWidth = true) => { + if (!isSmallScreenWidth) { + return; + } + this.keyboardDidHideListener = Keyboard.addListener( + 'keyboardDidHide', + () => { + Composer.setShouldShowComposeInput(shouldShowComposeInput); + }, + ); +}; diff --git a/src/libs/toggleReportActionComposeView/index.ios.js b/src/libs/toggleReportActionComposeView/index.ios.js new file mode 100644 index 000000000000..cb5f3690fe6f --- /dev/null +++ b/src/libs/toggleReportActionComposeView/index.ios.js @@ -0,0 +1,14 @@ +import {Keyboard} from 'react-native'; +import * as Composer from '../actions/Composer'; + +export default (shouldShowComposeInput, isSmallScreenWidth = true) => { + if (!isSmallScreenWidth) { + return; + } + this.keyboardDidHideListener = Keyboard.addListener( + 'keyboardDidHide', + () => { + Composer.setShouldShowComposeInput(shouldShowComposeInput); + }, + ); +}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 5f6b5e36632e..d6df89ab1782 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -71,6 +71,7 @@ class ReportActionItemMessageEdit extends React.Component { this.saveButtonID = 'saveButton'; this.cancelButtonID = 'cancelButton'; this.emojiButtonID = 'emojiButton'; + this.messageEditInput = 'MessageEditInput'; const parser = new ExpensiMark(); const draftMessage = parser.htmlToMarkdown(this.props.draftMessage); @@ -245,7 +246,7 @@ class ReportActionItemMessageEdit extends React.Component { }} onBlur={(event) => { // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering - if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID], lodashGet(event, 'nativeEvent.relatedTarget.id'))) { + if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID, this.messageEditInput], lodashGet(event, 'nativeEvent.relatedTarget.id'))) { return; } this.setState({isFocused: false}); From f7b3c5c2cf6f8e1d8ae683e42a106a4c0fdfea62 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 Feb 2023 14:20:40 +0700 Subject: [PATCH 090/108] fix: toggle composer input only when the keyboard hide completely --- .../toggleReportActionComposeView/index.android.js | 14 -------------- .../toggleReportActionComposeView/index.ios.js | 14 -------------- .../toggleReportActionComposeView/index.native.js | 10 +++++++++- .../home/report/ReportActionItemMessageEdit.js | 5 ++++- 4 files changed, 13 insertions(+), 30 deletions(-) delete mode 100644 src/libs/toggleReportActionComposeView/index.android.js delete mode 100644 src/libs/toggleReportActionComposeView/index.ios.js diff --git a/src/libs/toggleReportActionComposeView/index.android.js b/src/libs/toggleReportActionComposeView/index.android.js deleted file mode 100644 index cb5f3690fe6f..000000000000 --- a/src/libs/toggleReportActionComposeView/index.android.js +++ /dev/null @@ -1,14 +0,0 @@ -import {Keyboard} from 'react-native'; -import * as Composer from '../actions/Composer'; - -export default (shouldShowComposeInput, isSmallScreenWidth = true) => { - if (!isSmallScreenWidth) { - return; - } - this.keyboardDidHideListener = Keyboard.addListener( - 'keyboardDidHide', - () => { - Composer.setShouldShowComposeInput(shouldShowComposeInput); - }, - ); -}; diff --git a/src/libs/toggleReportActionComposeView/index.ios.js b/src/libs/toggleReportActionComposeView/index.ios.js deleted file mode 100644 index cb5f3690fe6f..000000000000 --- a/src/libs/toggleReportActionComposeView/index.ios.js +++ /dev/null @@ -1,14 +0,0 @@ -import {Keyboard} from 'react-native'; -import * as Composer from '../actions/Composer'; - -export default (shouldShowComposeInput, isSmallScreenWidth = true) => { - if (!isSmallScreenWidth) { - return; - } - this.keyboardDidHideListener = Keyboard.addListener( - 'keyboardDidHide', - () => { - Composer.setShouldShowComposeInput(shouldShowComposeInput); - }, - ); -}; diff --git a/src/libs/toggleReportActionComposeView/index.native.js b/src/libs/toggleReportActionComposeView/index.native.js index edebae2cba5b..4f782f26f682 100644 --- a/src/libs/toggleReportActionComposeView/index.native.js +++ b/src/libs/toggleReportActionComposeView/index.native.js @@ -1,3 +1,11 @@ +import {Keyboard} from 'react-native'; import * as Composer from '../actions/Composer'; -export default shouldShowComposeInput => Composer.setShouldShowComposeInput(shouldShowComposeInput); +export default (shouldShowComposeInput) => { + this.keyboardDidHideListener = Keyboard.addListener( + 'keyboardDidHide', + () => { + Composer.setShouldShowComposeInput(shouldShowComposeInput); + }, + ); +}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index d6df89ab1782..e057be6ac00a 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -71,7 +71,7 @@ class ReportActionItemMessageEdit extends React.Component { this.saveButtonID = 'saveButton'; this.cancelButtonID = 'cancelButton'; this.emojiButtonID = 'emojiButton'; - this.messageEditInput = 'MessageEditInput'; + this.messageEditInput = 'messageEditInput'; const parser = new ExpensiMark(); const draftMessage = parser.htmlToMarkdown(this.props.draftMessage); @@ -234,6 +234,7 @@ class ReportActionItemMessageEdit extends React.Component { this.textInput = el; this.props.forwardedRef(el); }} + nativeID={this.messageEditInput} onChangeText={this.updateDraft} // Debounced saveDraftComment onKeyPress={this.triggerSaveOrCancel} value={this.state.draft} @@ -245,6 +246,8 @@ class ReportActionItemMessageEdit extends React.Component { toggleReportActionComposeView(false, this.props.isSmallScreenWidth); }} onBlur={(event) => { + console.log("lodashGet(event, 'nativeEvent.relatedTarget.id')", lodashGet(event, 'nativeEvent.relatedTarget.id')); + // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID, this.messageEditInput], lodashGet(event, 'nativeEvent.relatedTarget.id'))) { return; From d41fad7749eace82493250fd38fbff79d6c4d9ad Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 Feb 2023 14:48:40 +0700 Subject: [PATCH 091/108] fix: toggle composer input only when the keyboard hide completely --- src/pages/home/report/ReportActionItemMessageEdit.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index e057be6ac00a..5d22eee38c7f 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -246,8 +246,6 @@ class ReportActionItemMessageEdit extends React.Component { toggleReportActionComposeView(false, this.props.isSmallScreenWidth); }} onBlur={(event) => { - console.log("lodashGet(event, 'nativeEvent.relatedTarget.id')", lodashGet(event, 'nativeEvent.relatedTarget.id')); - // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID, this.messageEditInput], lodashGet(event, 'nativeEvent.relatedTarget.id'))) { return; From ff2c8b2f74b2bb099c1b1b051908518d5eacb69d Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 Feb 2023 15:18:56 +0700 Subject: [PATCH 092/108] fix: toggle composer input only when the keyboard hide completely --- src/libs/toggleReportActionComposeView/index.native.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libs/toggleReportActionComposeView/index.native.js b/src/libs/toggleReportActionComposeView/index.native.js index 4f782f26f682..6a7dcdcd70ff 100644 --- a/src/libs/toggleReportActionComposeView/index.native.js +++ b/src/libs/toggleReportActionComposeView/index.native.js @@ -2,10 +2,8 @@ import {Keyboard} from 'react-native'; import * as Composer from '../actions/Composer'; export default (shouldShowComposeInput) => { - this.keyboardDidHideListener = Keyboard.addListener( - 'keyboardDidHide', - () => { - Composer.setShouldShowComposeInput(shouldShowComposeInput); - }, - ); + const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { + Composer.setShouldShowComposeInput(shouldShowComposeInput); + keyboardDidHideListener.remove(); + }); }; From e50dbb011165003aa1dcaff6cc057c894227d4cb Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 Feb 2023 15:33:29 +0700 Subject: [PATCH 093/108] fix: create toggleReportActionComposeViewWhenCloseEditMessage function only be triggered when close edit message --- src/libs/toggleReportActionComposeView/index.native.js | 8 +------- .../index.native.js | 9 +++++++++ src/pages/home/report/ReportActionItemMessageEdit.js | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js diff --git a/src/libs/toggleReportActionComposeView/index.native.js b/src/libs/toggleReportActionComposeView/index.native.js index 6a7dcdcd70ff..edebae2cba5b 100644 --- a/src/libs/toggleReportActionComposeView/index.native.js +++ b/src/libs/toggleReportActionComposeView/index.native.js @@ -1,9 +1,3 @@ -import {Keyboard} from 'react-native'; import * as Composer from '../actions/Composer'; -export default (shouldShowComposeInput) => { - const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - Composer.setShouldShowComposeInput(shouldShowComposeInput); - keyboardDidHideListener.remove(); - }); -}; +export default shouldShowComposeInput => Composer.setShouldShowComposeInput(shouldShowComposeInput); diff --git a/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js b/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js new file mode 100644 index 000000000000..488769741715 --- /dev/null +++ b/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js @@ -0,0 +1,9 @@ +import {Keyboard} from 'react-native'; +import * as Composer from '../actions/Composer'; + +export default () => { + const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { + Composer.setShouldShowComposeInput(true); + keyboardDidHideListener.remove(); + }); +}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 5d22eee38c7f..b8964cfe9431 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -10,6 +10,7 @@ import Composer from '../../../components/Composer'; import * as Report from '../../../libs/actions/Report'; import * as ReportScrollManager from '../../../libs/ReportScrollManager'; import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView'; +import toggleReportActionComposeViewWhenCloseEditMessage from '../../../libs/toggleReportActionComposeViewWhenCloseEditMessage'; import Button from '../../../components/Button'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; import compose from '../../../libs/compose'; @@ -251,7 +252,7 @@ class ReportActionItemMessageEdit extends React.Component { return; } this.setState({isFocused: false}); - toggleReportActionComposeView(true, this.props.isSmallScreenWidth); + toggleReportActionComposeViewWhenCloseEditMessage(true, this.props.isSmallScreenWidth); }} selection={this.state.selection} onSelectionChange={this.onSelectionChange} From 9dc3b00b135defd57ba71e66dce9aad98fe71b22 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 Feb 2023 15:48:27 +0700 Subject: [PATCH 094/108] fix: create toggleReportActionComposeViewWhenCloseEditMessage function only be triggered when close edit message --- .../index.js | 9 +++++++++ src/pages/home/report/ReportActionItemMessageEdit.js | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js diff --git a/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js b/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js new file mode 100644 index 000000000000..86594220aaa0 --- /dev/null +++ b/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js @@ -0,0 +1,9 @@ +import * as Composer from '../actions/Composer'; + +export default (isSmallScreenWidth = true) => { + if (!isSmallScreenWidth) { + return; + } + + Composer.setShouldShowComposeInput(true); +}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index b8964cfe9431..3d7aafb4e956 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -252,7 +252,7 @@ class ReportActionItemMessageEdit extends React.Component { return; } this.setState({isFocused: false}); - toggleReportActionComposeViewWhenCloseEditMessage(true, this.props.isSmallScreenWidth); + toggleReportActionComposeViewWhenCloseEditMessage(this.props.isSmallScreenWidth); }} selection={this.state.selection} onSelectionChange={this.onSelectionChange} From ffda9fc16fbd8059543e8b259b925a05832754c9 Mon Sep 17 00:00:00 2001 From: christianwenifr Date: Thu, 16 Feb 2023 16:17:42 +0700 Subject: [PATCH 095/108] fix: issue --- .../index.js | 5 +++++ .../index.native.js | 9 +++++++++ src/pages/home/report/ReportActionItemMessageEdit.js | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.js create mode 100644 src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.native.js diff --git a/src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.js b/src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.js new file mode 100644 index 000000000000..1174b409c4d6 --- /dev/null +++ b/src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.js @@ -0,0 +1,5 @@ +import toggleReportActionComposeView from '../toggleReportActionComposeView'; + +export default (isSmallScreenWidth = true) => { + toggleReportActionComposeView(true, isSmallScreenWidth); +}; diff --git a/src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.native.js b/src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.native.js new file mode 100644 index 000000000000..3bbfb73d30fc --- /dev/null +++ b/src/libs/openReportActionComposeViewWhenClosingMessageEdit/index.native.js @@ -0,0 +1,9 @@ +import {Keyboard} from 'react-native'; +import toggleReportActionComposeView from '../toggleReportActionComposeView'; + +export default (isSmallScreenWidth = true) => { + const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { + toggleReportActionComposeView(true, isSmallScreenWidth); + keyboardDidHideListener.remove(); + }); +}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 3d7aafb4e956..abfa76f740e8 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -10,7 +10,7 @@ import Composer from '../../../components/Composer'; import * as Report from '../../../libs/actions/Report'; import * as ReportScrollManager from '../../../libs/ReportScrollManager'; import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView'; -import toggleReportActionComposeViewWhenCloseEditMessage from '../../../libs/toggleReportActionComposeViewWhenCloseEditMessage'; +import openReportActionComposeViewWhenClosingMessageEdit from '../../../libs/openReportActionComposeViewWhenClosingMessageEdit'; import Button from '../../../components/Button'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; import compose from '../../../libs/compose'; @@ -252,7 +252,7 @@ class ReportActionItemMessageEdit extends React.Component { return; } this.setState({isFocused: false}); - toggleReportActionComposeViewWhenCloseEditMessage(this.props.isSmallScreenWidth); + openReportActionComposeViewWhenClosingMessageEdit(this.props.isSmallScreenWidth); }} selection={this.state.selection} onSelectionChange={this.onSelectionChange} From 50bb6bb43081ac47c03e157afbd78d0ea5d6ad44 Mon Sep 17 00:00:00 2001 From: christianwenifr Date: Thu, 16 Feb 2023 16:18:09 +0700 Subject: [PATCH 096/108] fix: delete redundant --- .../index.js | 9 --------- .../index.native.js | 9 --------- 2 files changed, 18 deletions(-) delete mode 100644 src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js delete mode 100644 src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js diff --git a/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js b/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js deleted file mode 100644 index 86594220aaa0..000000000000 --- a/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Composer from '../actions/Composer'; - -export default (isSmallScreenWidth = true) => { - if (!isSmallScreenWidth) { - return; - } - - Composer.setShouldShowComposeInput(true); -}; diff --git a/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js b/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js deleted file mode 100644 index 488769741715..000000000000 --- a/src/libs/toggleReportActionComposeViewWhenCloseEditMessage/index.native.js +++ /dev/null @@ -1,9 +0,0 @@ -import {Keyboard} from 'react-native'; -import * as Composer from '../actions/Composer'; - -export default () => { - const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - Composer.setShouldShowComposeInput(true); - keyboardDidHideListener.remove(); - }); -}; From aafc79f3587ea0b8209468021761f92f556184fe Mon Sep 17 00:00:00 2001 From: christianwenifr Date: Thu, 16 Feb 2023 16:26:22 +0700 Subject: [PATCH 097/108] fix: mweb bug --- src/pages/home/report/ReportActionItemMessageEdit.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index abfa76f740e8..abef7beb65ca 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -247,11 +247,17 @@ class ReportActionItemMessageEdit extends React.Component { toggleReportActionComposeView(false, this.props.isSmallScreenWidth); }} onBlur={(event) => { + const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id'); + // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering - if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID, this.messageEditInput], lodashGet(event, 'nativeEvent.relatedTarget.id'))) { + if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID], relatedTargetId)) { return; } this.setState({isFocused: false}); + + if (this.messageEditInput === relatedTargetId) { + return; + } openReportActionComposeViewWhenClosingMessageEdit(this.props.isSmallScreenWidth); }} selection={this.state.selection} From b8f485472afde269ab0815a91e2de97afc23da2b Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Thu, 16 Feb 2023 11:18:18 -0800 Subject: [PATCH 098/108] Revert "Conditionally require password for adding a debit card" --- .../settings/Payments/AddDebitCardPage.js | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/pages/settings/Payments/AddDebitCardPage.js b/src/pages/settings/Payments/AddDebitCardPage.js index cb55aee243c2..542979a2b279 100644 --- a/src/pages/settings/Payments/AddDebitCardPage.js +++ b/src/pages/settings/Payments/AddDebitCardPage.js @@ -21,7 +21,6 @@ import ONYXKEYS from '../../../ONYXKEYS'; import AddressSearch from '../../../components/AddressSearch'; import * as ComponentUtils from '../../../libs/ComponentUtils'; import Form from '../../../components/Form'; -import Permissions from '../../../libs/Permissions'; const propTypes = { /* Onyx Props */ @@ -29,9 +28,6 @@ const propTypes = { setupComplete: PropTypes.bool, }), - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - ...withLocalizePropTypes, }; @@ -39,7 +35,6 @@ const defaultProps = { formData: { setupComplete: false, }, - betas: [], }; class DebitCardPage extends Component { @@ -100,7 +95,7 @@ class DebitCardPage extends Component { errors.addressState = this.props.translate('addDebitCardPage.error.addressState'); } - if (!Permissions.canUsePasswordlessLogins(this.props.betas) && (!values.password || _.isEmpty(values.password.trim()))) { + if (!values.password || _.isEmpty(values.password.trim())) { errors.password = this.props.translate('addDebitCardPage.error.password'); } @@ -181,17 +176,15 @@ class DebitCardPage extends Component { /> - {!Permissions.canUsePasswordlessLogins(this.props.betas) && ( - - - - )} + + + ( @@ -219,8 +212,5 @@ export default compose( formData: { key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, }, - betas: { - key: ONYXKEYS.BETAS, - }, }), )(DebitCardPage); From 96ff340ce0f05a33aa9b931d0d6bf15f7867350b Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 16 Feb 2023 19:43:33 +0000 Subject: [PATCH 099/108] Update version to 1.2.72-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index dcb7a2e2f4c5..b33d37c2abda 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -156,8 +156,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001027200 - versionName "1.2.72-0" + versionCode 1001027201 + versionName "1.2.72-1" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 551df13bf1da..c375913706f7 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.2.72.0 + 1.2.72.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index f2b2a55a0f51..06cd7d25b422 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.2.72.0 + 1.2.72.1 diff --git a/package-lock.json b/package-lock.json index 67bafbd043d0..86ff94a0f51d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.2.72-0", + "version": "1.2.72-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.2.72-0", + "version": "1.2.72-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 53df3bda119f..dd0bbaffeaca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.2.72-0", + "version": "1.2.72-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.", From bad208941cd44dcc2fe775eae3d94cd7f9be483c Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:46:18 +0100 Subject: [PATCH 100/108] Renamed excludeNodes to excludedNodes --- src/libs/KeyboardShortcut/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/KeyboardShortcut/index.js b/src/libs/KeyboardShortcut/index.js index 1b6faa948d17..39c3a49e0609 100644 --- a/src/libs/KeyboardShortcut/index.js +++ b/src/libs/KeyboardShortcut/index.js @@ -75,8 +75,8 @@ function bindHandlerToKeydownEvent(event) { // Loop over all the callbacks _.every(eventHandlers[displayName], (callback) => { - // Early return for excludeNodes - if (_.contains(callback.excludeNodes, event.target.nodeName)) { + // Early return for excludedNodes + if (_.contains(callback.excludedNodes, event.target.nodeName)) { return true; } @@ -150,10 +150,10 @@ function getPlatformEquivalentForKeys(keys) { * @param {Boolean|Function} [shouldBubble] Should the event bubble? * @param {Number} [priority] The position the callback should take in the stack. 0 means top priority, and 1 means less priority than the most recently added. * @param {Boolean} [shouldPreventDefault] Should call event.preventDefault after callback? - * @param {Array} [excludeNodes] Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) + * @param {Array} [excludedNodes] Do not capture key events targeting excluded nodes (i.e. do not prevent default and let the event bubble) * @returns {Function} clean up method */ -function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, excludeNodes = []) { +function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOnInputs = false, shouldBubble = false, priority = 0, shouldPreventDefault = true, excludedNodes = []) { const platformAdjustedModifiers = getPlatformEquivalentForKeys(modifiers); const displayName = getDisplayName(key, platformAdjustedModifiers); if (!_.has(eventHandlers, displayName)) { @@ -167,7 +167,7 @@ function subscribe(key, callback, descriptionKey, modifiers = 'shift', captureOn captureOnInputs, shouldPreventDefault, shouldBubble, - excludeNodes, + excludedNodes, }); if (descriptionKey) { From 58d4d6a426f32c7b1a20dad4f827be6f4163efbb Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Thu, 16 Feb 2023 21:01:49 +0100 Subject: [PATCH 101/108] [Fix] Exclude policy rooms from getChatByParticipants results --- src/libs/ReportUtils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0a4fdcdebf87..7c3f21a0ee87 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1386,7 +1386,9 @@ function getChatByParticipants(newParticipantList) { if (!report || !report.participants) { return false; } - return _.isEqual(newParticipantList, report.participants.sort()); + + // Only return the room if it has all the participants and is not a policy room + return !isUserCreatedPolicyRoom(report) && _.isEqual(newParticipantList, report.participants.sort()); }); } From 06f7800786ffdf06ac1cb1032478471dc511035d Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 16 Feb 2023 21:11:56 +0000 Subject: [PATCH 102/108] Update version to 1.2.73-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b33d37c2abda..a3f027cccbab 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -156,8 +156,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001027201 - versionName "1.2.72-1" + versionCode 1001027300 + versionName "1.2.73-0" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index c375913706f7..19353bbdee98 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.72 + 1.2.73 CFBundleSignature ???? CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 1.2.72.1 + 1.2.73.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 06cd7d25b422..1265c4cc0127 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.2.72 + 1.2.73 CFBundleSignature ???? CFBundleVersion - 1.2.72.1 + 1.2.73.0 diff --git a/package-lock.json b/package-lock.json index 86ff94a0f51d..9b9695fc0237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.2.72-1", + "version": "1.2.73-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.2.72-1", + "version": "1.2.73-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index dd0bbaffeaca..9f788d542f07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.2.72-1", + "version": "1.2.73-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.", From 750c0b0b41cd33839a211ce45d70120b4c288b5f Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 16 Feb 2023 21:16:01 +0000 Subject: [PATCH 103/108] Update version to 1.2.73-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a3f027cccbab..fddc29abb18e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -156,8 +156,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001027300 - versionName "1.2.73-0" + versionCode 1001027301 + versionName "1.2.73-1" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 19353bbdee98..4c205fc54791 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.2.73.0 + 1.2.73.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1265c4cc0127..e6016d5654dc 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.2.73.0 + 1.2.73.1 diff --git a/package-lock.json b/package-lock.json index 9b9695fc0237..af9f3d2a1103 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.2.73-0", + "version": "1.2.73-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.2.73-0", + "version": "1.2.73-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 9f788d542f07..1ba4b4826559 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.2.73-0", + "version": "1.2.73-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.", From 4c504bea69455bddffa1a6ac99918bfa08aa2afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Thu, 16 Feb 2023 19:10:22 -0600 Subject: [PATCH 104/108] add new checklist item --- .github/PULL_REQUEST_TEMPLATE.md | 1 + contributingGuides/REVIEWER_CHECKLIST.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 565bf3d7fc3e..8ed9cd8ab8ff 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -111,6 +111,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases) - [ ] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. +- [ ] If the `main` branch was merged into this PR, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR. ### Screenshots/Videos diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index 616835a8b9f3..ea2c7103a76d 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -49,6 +49,7 @@ - [ ] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases) - [ ] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. +- [ ] If the `main` branch was merged into this PR, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR. ### Screenshots/Videos From 366bf276d7ea4d0073782b388dac71cef2fb0332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Thu, 16 Feb 2023 19:13:42 -0600 Subject: [PATCH 105/108] make it clear that it's after a review --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- contributingGuides/REVIEWER_CHECKLIST.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8ed9cd8ab8ff..ae27adabbab5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -111,7 +111,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases) - [ ] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. -- [ ] If the `main` branch was merged into this PR, I tested again and verified the outcome was still expected according to the `Test` steps. +- [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR. ### Screenshots/Videos diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index ea2c7103a76d..f81b56b088ec 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -49,7 +49,7 @@ - [ ] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases) - [ ] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. -- [ ] If the `main` branch was merged into this PR, I tested again and verified the outcome was still expected according to the `Test` steps. +- [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR. ### Screenshots/Videos From 95e037a6edab164cd3c8c68231ea344fbe75286e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 Feb 2023 19:13:24 -0800 Subject: [PATCH 106/108] fix undefined personal details --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 9326137f0a73..8ddec274eed8 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -58,7 +58,7 @@ let currentUserPersonalDetails; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS, callback: (val) => { - currentUserPersonalDetails = lodashGet(val, currentUserEmail); + currentUserPersonalDetails = lodashGet(val, currentUserEmail, {}); allPersonalDetails = val; }, }); From eef45b7dc23deb14346b83173c789afe4fd44063 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 17 Feb 2023 00:23:49 -0800 Subject: [PATCH 107/108] fix iou hint text does not show on policy expense chat --- src/components/ReportWelcomeText.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js index 7caac4156df4..ebfce58f3b0a 100644 --- a/src/components/ReportWelcomeText.js +++ b/src/components/ReportWelcomeText.js @@ -125,12 +125,12 @@ const ReportWelcomeText = (props) => { {(index < displayNamesWithTooltips.length - 2) && , } ))} - {(iouOptions.includes(CONST.IOU.IOU_TYPE.SEND) || iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)) && ( - - {/* Need to confirm copy for the below with marketing, and then add to translations. */} - {props.translate('reportActionsView.usePlusButton')} - - )} + + )} + {(iouOptions.includes(CONST.IOU.IOU_TYPE.SEND) || iouOptions.includes(CONST.IOU.IOU_TYPE.REQUEST)) && ( + + {/* Need to confirm copy for the below with marketing, and then add to translations. */} + {props.translate('reportActionsView.usePlusButton')} )} From c257f9255f336a858c69a77b3dfe625daf2291b6 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 17 Feb 2023 20:17:34 +0000 Subject: [PATCH 108/108] Update version to 1.2.74-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fddc29abb18e..e3983beb88a2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -156,8 +156,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001027301 - versionName "1.2.73-1" + versionCode 1001027400 + versionName "1.2.74-0" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 4c205fc54791..d4fdca7e787f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.73 + 1.2.74 CFBundleSignature ???? CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 1.2.73.1 + 1.2.74.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e6016d5654dc..ec0bf579022b 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.2.73 + 1.2.74 CFBundleSignature ???? CFBundleVersion - 1.2.73.1 + 1.2.74.0 diff --git a/package-lock.json b/package-lock.json index 3e5113e5a04c..e21f2bd26e1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.2.73-1", + "version": "1.2.74-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.2.73-1", + "version": "1.2.74-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3eb4c0827443..4ac642caa6e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.2.73-1", + "version": "1.2.74-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.",