From 0843b661532c7e8e88fd105473ede37e4af848fd Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 25 Aug 2024 06:29:51 +0530 Subject: [PATCH 001/225] fix: Dupe detect - Tax field does not show the Default label on the confirmation page. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 25 ++++++++++++++++--- .../ReviewDescription.tsx | 3 ++- src/types/onyx/ReviewDuplicates.ts | 4 +++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index e5bd5d9b0753..986435041fa6 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -959,9 +959,27 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia ]; } + const getTransactionCommentWithoutWaypointKeys = (firstTransaction: OnyxEntry) => { + // Remove keyForList from each waypoint + const updatedWaypoints = Object.fromEntries(Object.entries(firstTransaction?.waypoints ?? {}).map(([key, waypoint]) => [key, (({keyForList, ...rest}) => rest)(waypoint)])); + + // Create the new object + const updatedData = { + ...firstTransaction, + waypoints: updatedWaypoints, + }; + + return updatedData; + }; + // Helper function to check if all comments are equal function areAllCommentsEqual(items: Array>, firstTransaction: OnyxEntry) { - return items.every((item) => lodashIsEqual(item?.comment, firstTransaction?.comment)); + return items.every((item) => { + if (item?.comment?.waypoints) { + return lodashIsEqual(getTransactionCommentWithoutWaypointKeys(item.comment), getTransactionCommentWithoutWaypointKeys(firstTransaction?.comment)); + } + return lodashIsEqual(item?.comment, firstTransaction?.comment); + }); } // Helper function to check if all fields are equal for a given key @@ -986,10 +1004,11 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); - const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => item?.comment === undefined); + const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => !item?.comment); if (allCommentsAreEqual || allCommentsAreEmpty) { keep[fieldName] = firstTransaction?.comment?.comment ?? firstTransaction?.comment; + keep.comment = firstTransaction?.comment; } else { processChanges(fieldName, transactions, keys); } @@ -1027,7 +1046,7 @@ function buildNewTransactionAfterReviewingDuplicates(reviewDuplicateTransaction: ...restReviewDuplicateTransaction, modifiedMerchant: reviewDuplicateTransaction?.merchant, merchant: reviewDuplicateTransaction?.merchant, - comment: {comment: reviewDuplicateTransaction?.description}, + comment: {...reviewDuplicateTransaction?.comment, comment: reviewDuplicateTransaction?.description}, }; } diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index e6229afe48ac..be7bb7ca4b18 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -33,7 +33,8 @@ function ReviewDescription() { ); const setDescription = (data: FieldItemType<'description'>) => { if (data.value !== undefined) { - setReviewDuplicatesKey({description: data.value}); + const comment = compareResult.change.description?.find((d) => d?.comment === data.value); + setReviewDuplicatesKey({description: data.value, comment}); } navigateToNextScreen(); }; diff --git a/src/types/onyx/ReviewDuplicates.ts b/src/types/onyx/ReviewDuplicates.ts index 0682ed0a7f7c..e8944a9f56b0 100644 --- a/src/types/onyx/ReviewDuplicates.ts +++ b/src/types/onyx/ReviewDuplicates.ts @@ -1,3 +1,5 @@ +import type {Comment} from './Transaction'; + /** * Model of review duplicates request */ @@ -26,6 +28,8 @@ type ReviewDuplicates = { /** Description which user want to keep */ description: string; + /** Description which user want to keep */ + comment: Comment; /** Whether the transaction is reimbursable */ reimbursable: boolean; From ccfc7d86c09920aacf195b711a1b5c7cf43ab68a Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 11 Oct 2024 20:21:48 +0530 Subject: [PATCH 002/225] revert changes. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 25 +++---------------- .../ReviewDescription.tsx | 3 +-- src/types/onyx/ReviewDuplicates.ts | 4 --- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 986435041fa6..e5bd5d9b0753 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -959,27 +959,9 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia ]; } - const getTransactionCommentWithoutWaypointKeys = (firstTransaction: OnyxEntry) => { - // Remove keyForList from each waypoint - const updatedWaypoints = Object.fromEntries(Object.entries(firstTransaction?.waypoints ?? {}).map(([key, waypoint]) => [key, (({keyForList, ...rest}) => rest)(waypoint)])); - - // Create the new object - const updatedData = { - ...firstTransaction, - waypoints: updatedWaypoints, - }; - - return updatedData; - }; - // Helper function to check if all comments are equal function areAllCommentsEqual(items: Array>, firstTransaction: OnyxEntry) { - return items.every((item) => { - if (item?.comment?.waypoints) { - return lodashIsEqual(getTransactionCommentWithoutWaypointKeys(item.comment), getTransactionCommentWithoutWaypointKeys(firstTransaction?.comment)); - } - return lodashIsEqual(item?.comment, firstTransaction?.comment); - }); + return items.every((item) => lodashIsEqual(item?.comment, firstTransaction?.comment)); } // Helper function to check if all fields are equal for a given key @@ -1004,11 +986,10 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); - const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => !item?.comment); + const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => item?.comment === undefined); if (allCommentsAreEqual || allCommentsAreEmpty) { keep[fieldName] = firstTransaction?.comment?.comment ?? firstTransaction?.comment; - keep.comment = firstTransaction?.comment; } else { processChanges(fieldName, transactions, keys); } @@ -1046,7 +1027,7 @@ function buildNewTransactionAfterReviewingDuplicates(reviewDuplicateTransaction: ...restReviewDuplicateTransaction, modifiedMerchant: reviewDuplicateTransaction?.merchant, merchant: reviewDuplicateTransaction?.merchant, - comment: {...reviewDuplicateTransaction?.comment, comment: reviewDuplicateTransaction?.description}, + comment: {comment: reviewDuplicateTransaction?.description}, }; } diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index be7bb7ca4b18..e6229afe48ac 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -33,8 +33,7 @@ function ReviewDescription() { ); const setDescription = (data: FieldItemType<'description'>) => { if (data.value !== undefined) { - const comment = compareResult.change.description?.find((d) => d?.comment === data.value); - setReviewDuplicatesKey({description: data.value, comment}); + setReviewDuplicatesKey({description: data.value}); } navigateToNextScreen(); }; diff --git a/src/types/onyx/ReviewDuplicates.ts b/src/types/onyx/ReviewDuplicates.ts index e8944a9f56b0..0682ed0a7f7c 100644 --- a/src/types/onyx/ReviewDuplicates.ts +++ b/src/types/onyx/ReviewDuplicates.ts @@ -1,5 +1,3 @@ -import type {Comment} from './Transaction'; - /** * Model of review duplicates request */ @@ -28,8 +26,6 @@ type ReviewDuplicates = { /** Description which user want to keep */ description: string; - /** Description which user want to keep */ - comment: Comment; /** Whether the transaction is reimbursable */ reimbursable: boolean; From 76ab756af59c94d9f9ff35bcb74a26e0ccb80330 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 17 Oct 2024 19:07:11 +0530 Subject: [PATCH 003/225] update main branch. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 3 ++- src/pages/TransactionDuplicate/ReviewDescription.tsx | 4 +++- src/types/onyx/ReviewDuplicates.ts | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index bd0db668fe2c..1630d74835cb 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1045,6 +1045,7 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => getDescription(item) === ''); if (allCommentsAreEqual || allCommentsAreEmpty) { keep[fieldName] = firstTransaction?.comment?.comment ?? firstTransaction?.comment; + keep.comment = firstTransaction?.comment; } else { processChanges(fieldName, transactions, keys); } @@ -1082,7 +1083,7 @@ function buildNewTransactionAfterReviewingDuplicates(reviewDuplicateTransaction: ...restReviewDuplicateTransaction, modifiedMerchant: reviewDuplicateTransaction?.merchant, merchant: reviewDuplicateTransaction?.merchant, - comment: {comment: reviewDuplicateTransaction?.description}, + comment: {...reviewDuplicateTransaction?.comment, comment: reviewDuplicateTransaction?.description}, }; } diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index 3d74d8cc36e1..108a39d654ce 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -38,7 +38,9 @@ function ReviewDescription() { ); const setDescription = (data: FieldItemType<'description'>) => { if (data.value !== undefined) { - setReviewDuplicatesKey({description: data.value}); + // setReviewDuplicatesKey({description: data.value}); + const comment = compareResult.change.description?.find((d) => d?.comment === data.value); + setReviewDuplicatesKey({comment, description: data.value}); } navigateToNextScreen(); }; diff --git a/src/types/onyx/ReviewDuplicates.ts b/src/types/onyx/ReviewDuplicates.ts index 0682ed0a7f7c..f436b50b1679 100644 --- a/src/types/onyx/ReviewDuplicates.ts +++ b/src/types/onyx/ReviewDuplicates.ts @@ -1,3 +1,5 @@ +import type {Comment} from './Transaction'; + /** * Model of review duplicates request */ @@ -26,6 +28,9 @@ type ReviewDuplicates = { /** Description which user want to keep */ description: string; + /** Description which user want to keep */ + comment: Comment; + /** Whether the transaction is reimbursable */ reimbursable: boolean; From c8084ffbc9476592e3f7203632e42ba36bed92f6 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 20 Oct 2024 00:44:43 +0200 Subject: [PATCH 004/225] Hide approve button if report has violations --- src/libs/actions/IOU.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index fb8cd014ec7b..047d081fbacb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6988,8 +6988,9 @@ function canApproveIOU(iouReport: OnyxTypes.OnyxInputOrEntry, const reportNameValuePairs = ReportUtils.getReportNameValuePairs(iouReport?.reportID); const isArchivedReport = ReportUtils.isArchivedRoom(iouReport, reportNameValuePairs); const unheldTotalIsZero = iouReport && iouReport.unheldTotal === 0; + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); - return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled && !isArchivedReport && !unheldTotalIsZero; + return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled && !isArchivedReport && !unheldTotalIsZero && !hasViolations; } function canIOUBePaid( From f63804fda2685824aee85d1a1f9eba15d0bed65e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 25 Oct 2024 19:24:52 +0800 Subject: [PATCH 005/225] fix hidden shows briefly when mentioning unknown user --- .../HTMLRenderers/MentionUserRenderer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index 36586b09e514..96bdf8e9e1e8 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -4,9 +4,9 @@ import isEmpty from 'lodash/isEmpty'; import React from 'react'; import {StyleSheet} from 'react-native'; import type {TextStyle} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {TNodeChildrenRenderer} from 'react-native-render-html'; -import {usePersonalDetails} from '@components/OnyxProvider'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; @@ -20,6 +20,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import asMutable from '@src/types/utils/asMutable'; @@ -31,7 +32,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const htmlAttribAccountID = tnode.attributes.accountid; - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const htmlAttributeAccountID = tnode.attributes.accountid; let accountID: number; @@ -56,7 +57,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona return displayText.split('@').at(0); }; - if (!isEmpty(htmlAttribAccountID)) { + if (!isEmpty(htmlAttribAccountID) && personalDetails?.[htmlAttribAccountID]) { const user = personalDetails[htmlAttribAccountID]; accountID = parseInt(htmlAttribAccountID, 10); mentionDisplayText = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') || PersonalDetailsUtils.getDisplayNameOrDefault(user); From dd9c2d426f05a146d6f89bd35089eafc8aa7a4de Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 29 Oct 2024 13:58:47 +0800 Subject: [PATCH 006/225] fix wrong backTo --- src/ROUTES.ts | 6 +++++- src/libs/Navigation/types.ts | 3 ++- src/libs/actions/BankAccounts.ts | 2 +- src/pages/settings/Wallet/PaymentMethodList.tsx | 2 +- src/pages/settings/Wallet/VerifyAccountPage.tsx | 8 ++++---- src/pages/settings/Wallet/WalletPage/WalletPage.tsx | 4 +++- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2e895537eaac..09f8fad58ff0 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -156,7 +156,11 @@ const ROUTES = { SETTINGS_ABOUT: 'settings/about', SETTINGS_APP_DOWNLOAD_LINKS: 'settings/about/app-download-links', SETTINGS_WALLET: 'settings/wallet', - SETTINGS_WALLET_VERIFY_ACCOUNT: {route: 'settings/wallet/verify', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/wallet/verify', backTo)}, + SETTINGS_WALLET_VERIFY_ACCOUNT: { + route: 'settings/wallet/verify', + getRoute: (backTo?: string, forwardTo?: string) => + getUrlWithBackToParam(forwardTo ? `settings/wallet/verify?forwardTo=${encodeURIComponent(forwardTo)}` : 'settings/wallet/verify', backTo), + }, SETTINGS_WALLET_DOMAINCARD: { route: 'settings/wallet/card/:cardID?', getRoute: (cardID: string) => `settings/wallet/card/${cardID}` as const, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3de07f2c801f..138e723345a3 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -108,7 +108,8 @@ type SettingsNavigatorParamList = { backTo?: Routes; }; [SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: { - backTo: Routes; + backTo?: Routes; + forwardTo?: Routes; }; [SCREENS.SETTINGS.PREFERENCES.ROOT]: undefined; [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: undefined; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 6679a6e4b9ea..4795c1524d0e 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -79,7 +79,7 @@ function openPersonalBankAccountSetupView(exitReportID?: string, isUserValidated Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID}); } if (!isUserValidated) { - Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_ADD_BANK_ACCOUNT)); + Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_WALLET, ROUTES.SETTINGS_ADD_BANK_ACCOUNT)); } Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); }); diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 23d0b5ab6550..9525c54f84f1 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -347,7 +347,7 @@ function PaymentMethodList({ const onPressItem = useCallback(() => { if (!isUserValidated) { - Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_ADD_BANK_ACCOUNT)); + Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_WALLET, ROUTES.SETTINGS_ADD_BANK_ACCOUNT)); return; } onPress(); diff --git a/src/pages/settings/Wallet/VerifyAccountPage.tsx b/src/pages/settings/Wallet/VerifyAccountPage.tsx index a3b51ef0de17..3275cea40495 100644 --- a/src/pages/settings/Wallet/VerifyAccountPage.tsx +++ b/src/pages/settings/Wallet/VerifyAccountPage.tsx @@ -8,7 +8,6 @@ import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as User from '@userActions/User'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type VerifyAccountPageProps = StackScreenProps; @@ -23,6 +22,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(true); + const navigateForwardTo = route.params?.forwardTo; const navigateBackTo = route?.params?.backTo; useEffect(() => () => User.clearUnvalidatedNewContactMethodAction(), []); @@ -49,8 +49,8 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { return; } - Navigation.navigate(navigateBackTo); - }, [isUserValidated, navigateBackTo]); + Navigation.navigate(navigateForwardTo); + }, [isUserValidated, navigateForwardTo]); useEffect(() => { if (isValidateCodeActionModalVisible) { @@ -58,7 +58,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { } if (!isUserValidated && navigateBackTo) { - Navigation.navigate(ROUTES.SETTINGS_WALLET); + Navigation.goBack(navigateBackTo); } else if (!navigateBackTo) { Navigation.goBack(); } diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 7b9366370349..3e3030c86d4f 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -500,7 +500,9 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { ref={buttonRef as ForwardedRef} onPress={() => { if (!isUserValidated) { - Navigation.navigate(ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_ENABLE_PAYMENTS)); + Navigation.navigate( + ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT.getRoute(ROUTES.SETTINGS_WALLET, ROUTES.SETTINGS_ENABLE_PAYMENTS), + ); return; } Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS); From 774851deb0e68c408e3f51c893f2f0ec05cc51b8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 29 Oct 2024 14:35:01 +0800 Subject: [PATCH 007/225] fix wrong variable being checked --- src/pages/settings/Wallet/VerifyAccountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/VerifyAccountPage.tsx b/src/pages/settings/Wallet/VerifyAccountPage.tsx index 3275cea40495..302f73f09144 100644 --- a/src/pages/settings/Wallet/VerifyAccountPage.tsx +++ b/src/pages/settings/Wallet/VerifyAccountPage.tsx @@ -45,7 +45,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { setIsValidateCodeActionModalVisible(false); - if (!navigateBackTo) { + if (!navigateForwardTo) { return; } From 3204148992ce2e3927608e2c6359439a17bd313f Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:39:52 +0100 Subject: [PATCH 008/225] Disable the pay button if the report has violaitons --- src/libs/actions/IOU.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 047d081fbacb..f0461d03dd26 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7042,8 +7042,9 @@ function canIOUBePaid( const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); const shouldBeApproved = canApproveIOU(iouReport, policy); - + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); + return ( isPayer && !isOpenExpenseReport && @@ -7053,8 +7054,10 @@ function canIOUBePaid( !isChatReportArchived && !isAutoReimbursable && !shouldBeApproved && + !hasViolations && !isPayAtEndExpenseReport ); + } function getIOUReportActionToApproveOrPay(chatReport: OnyxEntry, excludedIOUReportID: string): OnyxEntry { From f760f272cdfd83f421fe929553a84c699d23a40a Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:40:51 +0100 Subject: [PATCH 009/225] minor edit --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f0461d03dd26..575e14e36fd3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7057,7 +7057,6 @@ function canIOUBePaid( !hasViolations && !isPayAtEndExpenseReport ); - } function getIOUReportActionToApproveOrPay(chatReport: OnyxEntry, excludedIOUReportID: string): OnyxEntry { From 38a56502c4168ba86d0ae449b8aa1fe03b7d69fa Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:45:12 +0100 Subject: [PATCH 010/225] minor edit --- src/libs/actions/IOU.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 575e14e36fd3..3a0ca373d3de 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7043,6 +7043,7 @@ function canIOUBePaid( const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); const shouldBeApproved = canApproveIOU(iouReport, policy); const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); return ( From 7af0e6a9cebac8ae5c2bc1c53ffd5b4833dc9193 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:46:17 +0100 Subject: [PATCH 011/225] minor edit --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3a0ca373d3de..575e14e36fd3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7043,7 +7043,6 @@ function canIOUBePaid( const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); const shouldBeApproved = canApproveIOU(iouReport, policy); const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); - const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); return ( From 6c716bb01bf104c444a4597c4cb140d9b99097bd Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:49:51 +0100 Subject: [PATCH 012/225] minor edit --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 575e14e36fd3..f911f17d0103 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7044,7 +7044,7 @@ function canIOUBePaid( const shouldBeApproved = canApproveIOU(iouReport, policy); const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); - + return ( isPayer && !isOpenExpenseReport && From 51eea863651c5506e80f13e1a9bd70e00fbf4060 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Nov 2024 12:05:23 +0100 Subject: [PATCH 013/225] Initial work on updating reanimated --- package-lock.json | 9 +- package.json | 2 +- .../AttachmentCarousel/Pager/index.tsx | 7 +- .../useCarouselContextEvents.ts | 9 +- .../AttachmentViewPdf/index.android.tsx | 24 +-- .../BaseAutoCompleteSuggestions.tsx | 31 ++-- .../AvatarCropModal/AvatarCropModal.tsx | 149 ++++++++---------- src/components/FloatingActionButton.tsx | 17 +- src/components/OpacityView.tsx | 9 +- src/components/SpacerView.tsx | 11 +- 10 files changed, 128 insertions(+), 140 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2f927adf13f..cf21c105a4ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", - "react-native-reanimated": "3.15.1", + "react-native-reanimated": "^3.16.1", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.10.9", @@ -35626,9 +35626,10 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.15.1.tgz", - "integrity": "sha512-DbBeUUExtJ1x1nfE94I8qgDgWjq5ztM3IO/+XFO+agOkPeVpBs5cRnxHfJKrjqJ2MgwhJOUDmtHxo+tDsoeitg==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.1.tgz", + "integrity": "sha512-Wnbo7toHZ6kPLAD8JWKoKCTfNoqYOMW5vUEP76Rr4RBmJCrdXj6oauYP0aZnZq8NCbiP5bwwu7+RECcWtoetnQ==", + "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", diff --git a/package.json b/package.json index 9620386b5560..6b22e123c174 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-quick-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", - "react-native-reanimated": "3.15.1", + "react-native-reanimated": "^3.16.1", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.10.9", diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx index 6fe32050b6d5..68a12c4cd76e 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx @@ -70,14 +70,13 @@ function AttachmentCarouselPager( const pageScrollHandler = usePageScrollHandler((e) => { 'worklet'; - // eslint-disable-next-line react-compiler/react-compiler - activePage.value = e.position; - isPagerScrolling.value = e.offset !== 0; + activePage.set(e.position); + isPagerScrolling.set(e.offset !== 0); }, []); useEffect(() => { setActivePageIndex(initialPage); - activePage.value = initialPage; + activePage.set(initialPage); }, [activePage, initialPage]); /** The `pagerItems` object that passed down to the context. Later used to detect current page, whether it's a single image gallery etc. */ diff --git a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts index 1c54d7841347..3311f6476194 100644 --- a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts +++ b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts @@ -35,12 +35,11 @@ function useCarouselContextEvents(setShouldShowArrows: (show?: SetStateAction { - if (!isScrollEnabled.value) { + if (!isScrollEnabled.get()) { return; } onRequestToggleArrows(); - }, [isScrollEnabled.value, onRequestToggleArrows]); + }, [isScrollEnabled, onRequestToggleArrows]); return {handleTap, handleScaleChange, scale, isScrollEnabled}; } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx index c6e7984b793f..c756345664cc 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx @@ -32,32 +32,32 @@ function AttachmentViewPdf(props: AttachmentViewPdfProps) { const Pan = Gesture.Pan() .manualActivation(true) .onTouchesMove((evt) => { - if (offsetX.value !== 0 && offsetY.value !== 0 && isScrollEnabled && scale.value === 1) { - const translateX = Math.abs((evt.allTouches.at(0)?.absoluteX ?? 0) - offsetX.value); - const translateY = Math.abs((evt.allTouches.at(0)?.absoluteY ?? 0) - offsetY.value); - const allowEnablingScroll = !isPanGestureActive.value || isScrollEnabled.value; + if (offsetX.get() !== 0 && offsetY.get() !== 0 && isScrollEnabled && scale.get() === 1) { + const translateX = Math.abs((evt.allTouches.at(0)?.absoluteX ?? 0) - offsetX.get()); + const translateY = Math.abs((evt.allTouches.at(0)?.absoluteY ?? 0) - offsetY.get()); + const allowEnablingScroll = !isPanGestureActive.get() || isScrollEnabled.get(); // if the value of X is greater than Y and the pdf is not zoomed in, // enable the pager scroll so that the user // can swipe to the next attachment otherwise disable it. if (translateX > translateY && translateX > SCROLL_THRESHOLD && allowEnablingScroll) { // eslint-disable-next-line react-compiler/react-compiler - isScrollEnabled.value = true; + isScrollEnabled.set(true); } else if (translateY > SCROLL_THRESHOLD) { - isScrollEnabled.value = false; + isScrollEnabled.set(false); } } - isPanGestureActive.value = true; - offsetX.value = evt.allTouches.at(0)?.absoluteX ?? 0; - offsetY.value = evt.allTouches.at(0)?.absoluteY ?? 0; + isPanGestureActive.set(true); + offsetX.set(evt.allTouches.at(0)?.absoluteX ?? 0); + offsetY.set(evt.allTouches.at(0)?.absoluteY ?? 0); }) .onTouchesUp(() => { - isPanGestureActive.value = false; + isPanGestureActive.set(false); if (!isScrollEnabled) { return; } - isScrollEnabled.value = scale.value === 1; + isScrollEnabled.set(scale.get() === 1); }); const Content = useMemo( @@ -69,7 +69,7 @@ function AttachmentViewPdf(props: AttachmentViewPdfProps) { // The react-native-pdf's onScaleChanged event will sometimes give us scale values of e.g. 0.99... instead of 1, // even though we're not pinching/zooming // Rounding the scale value to 2 decimal place fixes this issue, since pinching will still be possible but very small pinches are ignored. - scale.value = Math.round(newScale * 1e2) / 1e2; + scale.set(Math.round(newScale * 1e2) / 1e2); }} /> ), diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 2d22a2560bb0..abc221ed646a 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -50,24 +50,27 @@ function BaseAutoCompleteSuggestions({ const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; const animatedStyles = useAnimatedStyle(() => ({ - opacity: fadeInOpacity.value, - ...StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value), + opacity: fadeInOpacity.get(), + ...StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.get()), })); useEffect(() => { if (measuredHeightOfSuggestionRows === prevRowHeightRef.current) { - // eslint-disable-next-line react-compiler/react-compiler - fadeInOpacity.value = withTiming(1, { - duration: 70, - easing: Easing.inOut(Easing.ease), - }); - rowHeight.value = measuredHeightOfSuggestionRows; + fadeInOpacity.set( + withTiming(1, { + duration: 70, + easing: Easing.inOut(Easing.ease), + }), + ); + rowHeight.set(measuredHeightOfSuggestionRows); } else { - fadeInOpacity.value = 1; - rowHeight.value = withTiming(measuredHeightOfSuggestionRows, { - duration: 100, - easing: Easing.bezier(0.25, 0.1, 0.25, 1), - }); + fadeInOpacity.set(1); + rowHeight.set( + withTiming(measuredHeightOfSuggestionRows, { + duration: 100, + easing: Easing.bezier(0.25, 0.1, 0.25, 1), + }), + ); } prevRowHeightRef.current = measuredHeightOfSuggestionRows; @@ -103,7 +106,7 @@ function BaseAutoCompleteSuggestions({ renderItem={renderItem} keyExtractor={keyExtractor} removeClippedSubviews={false} - showsVerticalScrollIndicator={innerHeight > rowHeight.value} + showsVerticalScrollIndicator={innerHeight > rowHeight.get()} extraData={[highlightedSuggestionIndex, renderSuggestionMenuItem]} /> diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index dca0d08d11d5..a3c4cdca78bf 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -97,16 +97,16 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Changes the modal state values to initial const resetState = useCallback(() => { - originalImageWidth.value = CONST.AVATAR_CROP_MODAL.INITIAL_SIZE; - originalImageHeight.value = CONST.AVATAR_CROP_MODAL.INITIAL_SIZE; - translateY.value = 0; - translateX.value = 0; - scale.value = CONST.AVATAR_CROP_MODAL.MIN_SCALE; - rotation.value = 0; - translateSlider.value = 0; - prevMaxOffsetX.value = 0; - prevMaxOffsetY.value = 0; - isLoading.value = false; + originalImageWidth.set(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); + originalImageHeight.set(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); + translateY.set(0); + translateX.set(0); + scale.set(CONST.AVATAR_CROP_MODAL.MIN_SCALE); + rotation.set(0); + translateSlider.set(0); + prevMaxOffsetX.set(0); + prevMaxOffsetY.set(0); + isLoading.set(false); setImageContainerSize(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); setSliderContainerSize(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); setIsImageContainerInitialized(false); @@ -123,12 +123,11 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose ImageSize.getSize(imageUri).then(({width, height, rotation: orginalRotation}) => { // On Android devices ImageSize library returns also rotation parameter. if (orginalRotation === 90 || orginalRotation === 270) { - // eslint-disable-next-line react-compiler/react-compiler - originalImageHeight.value = width; - originalImageWidth.value = height; + originalImageHeight.set(width); + originalImageWidth.set(height); } else { - originalImageHeight.value = height; - originalImageWidth.value = width; + originalImageHeight.set(height); + originalImageWidth.set(width); } setIsImageInitialized(true); @@ -136,8 +135,8 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Because the reanimated library has some internal optimizations, // sometimes when the modal is hidden styles of the image and slider might not be updated. // To trigger the update we need to slightly change the following values: - translateSlider.value += 0.01; - rotation.value += 360; + translateSlider.set((value) => value + 0.01); + rotation.set((value) => value + 360); }); }, [imageUri, originalImageHeight, originalImageWidth, rotation, translateSlider]); @@ -150,15 +149,15 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose * Returns current image size taking into account scale and rotation. */ const getDisplayedImageSize = useWorkletCallback(() => { - let height = imageContainerSize * scale.value; - let width = imageContainerSize * scale.value; + let height = imageContainerSize * scale.get(); + let width = imageContainerSize * scale.get(); // Since the smaller side will be always equal to the imageContainerSize multiplied by scale, // another side can be calculated with aspect ratio. - if (originalImageWidth.value > originalImageHeight.value) { - width *= originalImageWidth.value / originalImageHeight.value; + if (originalImageWidth.get() > originalImageHeight.get()) { + width *= originalImageWidth.get() / originalImageHeight.get(); } else { - height *= originalImageHeight.value / originalImageWidth.value; + height *= originalImageHeight.get() / originalImageWidth.get(); } return {height, width}; @@ -172,10 +171,10 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const {height, width} = getDisplayedImageSize(); const maxOffsetX = (width - imageContainerSize) / 2; const maxOffsetY = (height - imageContainerSize) / 2; - translateX.value = clamp(offsetX, [maxOffsetX * -1, maxOffsetX]); - translateY.value = clamp(offsetY, [maxOffsetY * -1, maxOffsetY]); - prevMaxOffsetX.value = maxOffsetX; - prevMaxOffsetY.value = maxOffsetY; + translateX.set(clamp(offsetX, [maxOffsetX * -1, maxOffsetX])); + translateY.set(clamp(offsetY, [maxOffsetY * -1, maxOffsetY])); + prevMaxOffsetX.set(maxOffsetX); + prevMaxOffsetY.set(maxOffsetY); }, [imageContainerSize, scale, clamp], ); @@ -190,8 +189,8 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose * and updates image's offset. */ const panGesture = Gesture.Pan().onChange((event) => { - const newX = translateX.value + event.changeX; - const newY = translateY.value + event.changeY; + const newX = translateX.get() + event.changeX; + const newY = translateY.get() + event.changeY; updateImageOffset(newX, newY); }); @@ -200,7 +199,7 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // when the browser window is resized. useEffect(() => { // If no panning has happened and the value is 0, do an early return. - if (!prevMaxOffsetX.value && !prevMaxOffsetY.value) { + if (!prevMaxOffsetX.get() && !prevMaxOffsetY.get()) { return; } const {height, width} = getDisplayedImageSize(); @@ -210,14 +209,14 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Since interpolation is expensive, we only want to do it if // image has been panned across X or Y axis by the user. if (prevMaxOffsetX) { - translateX.value = interpolate(translateX.value, [prevMaxOffsetX.value * -1, prevMaxOffsetX.value], [maxOffsetX * -1, maxOffsetX]); + translateX.set(interpolate(translateX.get(), [prevMaxOffsetX.get() * -1, prevMaxOffsetX.get()], [maxOffsetX * -1, maxOffsetX])); } if (prevMaxOffsetY) { - translateY.value = interpolate(translateY.value, [prevMaxOffsetY.value * -1, prevMaxOffsetY.value], [maxOffsetY * -1, maxOffsetY]); + translateY.set(interpolate(translateY.get(), [prevMaxOffsetY.get() * -1, prevMaxOffsetY.get()], [maxOffsetY * -1, maxOffsetY])); } - prevMaxOffsetX.value = maxOffsetX; - prevMaxOffsetY.value = maxOffsetY; + prevMaxOffsetX.set(maxOffsetX); + prevMaxOffsetY.set(maxOffsetY); }, [getDisplayedImageSize, imageContainerSize, prevMaxOffsetX, prevMaxOffsetY, translateX, translateY]); /** @@ -228,65 +227,72 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose onBegin: () => { 'worklet'; - isPressableEnabled.value = false; + isPressableEnabled.set(false); }, onChange: (event: GestureUpdateEvent) => { 'worklet'; - const newSliderValue = clamp(translateSlider.value + event.changeX, [0, sliderContainerSize]); + const newSliderValue = clamp(translateSlider.get() + event.changeX, [0, sliderContainerSize]); const newScale = newScaleValue(newSliderValue, sliderContainerSize); - const differential = newScale / scale.value; + const differential = newScale / scale.get(); - scale.value = newScale; - translateSlider.value = newSliderValue; + scale.set(newScale); + translateSlider.set(newSliderValue); - const newX = translateX.value * differential; - const newY = translateY.value * differential; + const newX = translateX.get() * differential; + const newY = translateY.get() * differential; updateImageOffset(newX, newY); }, onFinalize: () => { 'worklet'; - isPressableEnabled.value = true; + isPressableEnabled.set(true); }, }; // This effect is needed to prevent the incorrect position of // the slider's knob when the window's layout changes useEffect(() => { - translateSlider.value = interpolate(scale.value, [CONST.AVATAR_CROP_MODAL.MIN_SCALE, CONST.AVATAR_CROP_MODAL.MAX_SCALE], [0, sliderContainerSize]); - }, [scale.value, sliderContainerSize, translateSlider]); + translateSlider.set(interpolate(scale.get(), [CONST.AVATAR_CROP_MODAL.MIN_SCALE, CONST.AVATAR_CROP_MODAL.MAX_SCALE], [0, sliderContainerSize])); + }, [scale, sliderContainerSize, translateSlider]); // Rotates the image by changing the rotation value by 90 degrees // and updating the position so the image remains in the same place after rotation const rotateImage = useCallback(() => { - rotation.value -= 90; + rotation.set((value) => value - 90); + + const oldX = translateX.get(); + const oldY = translateY.get(); + const oldHeight = originalImageHeight.get(); + const oldWidth = originalImageWidth.get(); // Rotating 2d coordinates by applying the formula (x, y) → (-y, x). - [translateX.value, translateY.value] = [translateY.value, translateX.value * -1]; + translateX.set(oldY); + translateY.set(oldX * -1); // Since we rotated the image by 90 degrees, now width becomes height and vice versa. - [originalImageHeight.value, originalImageWidth.value] = [originalImageWidth.value, originalImageHeight.value]; - }, [originalImageHeight.value, originalImageWidth.value, rotation, translateX.value, translateY.value]); + originalImageHeight.set(oldWidth); + originalImageWidth.set(oldHeight); + }, [originalImageHeight, originalImageWidth, rotation, translateX, translateY]); // Crops an image that was provided in the imageUri prop, using the current position/scale // then calls onSave and onClose callbacks const cropAndSaveImage = useCallback(() => { - if (isLoading.value) { + if (isLoading.get()) { return; } - isLoading.value = true; - const smallerSize = Math.min(originalImageHeight.value, originalImageWidth.value); - const size = smallerSize / scale.value; - const imageCenterX = originalImageWidth.value / 2; - const imageCenterY = originalImageHeight.value / 2; + isLoading.set(true); + const smallerSize = Math.min(originalImageHeight.get(), originalImageWidth.get()); + const size = smallerSize / scale.get(); + const imageCenterX = originalImageWidth.get() / 2; + const imageCenterY = originalImageHeight.get() / 2; const apothem = size / 2; // apothem for squares is equals to half of it size // Since the translate value is only a distance from the image center, we are able to calculate // the originX and the originY - start coordinates of cropping view. - const originX = imageCenterX - apothem - (translateX.value / imageContainerSize / scale.value) * smallerSize; - const originY = imageCenterY - apothem - (translateY.value / imageContainerSize / scale.value) * smallerSize; + const originX = imageCenterX - apothem - (translateX.get() / imageContainerSize / scale.get()) * smallerSize; + const originY = imageCenterY - apothem - (translateY.get() / imageContainerSize / scale.get()) * smallerSize; const crop = { height: size, @@ -301,29 +307,15 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const name = isSvg ? 'fileName.png' : imageName; const type = isSvg ? 'image/png' : imageType; - cropOrRotateImage(imageUri, [{rotate: rotation.value % 360}, {crop}], {compress: 1, name, type}) + cropOrRotateImage(imageUri, [{rotate: rotation.get() % 360}, {crop}], {compress: 1, name, type}) .then((newImage) => { onClose?.(); onSave?.(newImage); }) .catch(() => { - isLoading.value = false; + isLoading.set(false); }); - }, [ - imageUri, - imageName, - imageType, - onClose, - onSave, - originalImageHeight.value, - originalImageWidth.value, - scale.value, - translateX.value, - imageContainerSize, - translateY.value, - rotation.value, - isLoading, - ]); + }, [isLoading, originalImageHeight, originalImageWidth, scale, translateX, imageContainerSize, translateY, imageType, imageName, imageUri, rotation, onClose, onSave]); const sliderOnPress = (locationX: number) => { // We are using the worklet directive here and running on the UI thread to ensure the Reanimated @@ -331,17 +323,16 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose 'worklet'; - if (!locationX || !isPressableEnabled.value) { + if (!locationX || !isPressableEnabled.get()) { return; } const newSliderValue = clamp(locationX, [0, sliderContainerSize]); const newScale = newScaleValue(newSliderValue, sliderContainerSize); - // eslint-disable-next-line react-compiler/react-compiler - translateSlider.value = newSliderValue; - const differential = newScale / scale.value; - scale.value = newScale; - const newX = translateX.value * differential; - const newY = translateY.value * differential; + translateSlider.set(newSliderValue); + const differential = newScale / scale.get(); + scale.set(newScale); + const newX = translateX.get() * differential; + const newY = translateY.get() * differential; updateImageOffset(newX, newY); }; diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index ecf72f89134b..3c831301db8b 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -60,18 +60,19 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo const buttonRef = ref; useEffect(() => { - // eslint-disable-next-line react-compiler/react-compiler - sharedValue.value = withTiming(isActive ? 1 : 0, { - duration: 340, - easing: Easing.inOut(Easing.ease), - }); + sharedValue.set( + withTiming(isActive ? 1 : 0, { + duration: 340, + easing: Easing.inOut(Easing.ease), + }), + ); }, [isActive, sharedValue]); const animatedStyle = useAnimatedStyle(() => { - const backgroundColor = interpolateColor(sharedValue.value, [0, 1], [success, buttonDefaultBG]); + const backgroundColor = interpolateColor(sharedValue.get(), [0, 1], [success, buttonDefaultBG]); return { - transform: [{rotate: `${sharedValue.value * 135}deg`}], + transform: [{rotate: `${sharedValue.get() * 135}deg`}], backgroundColor, borderRadius, }; @@ -79,7 +80,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo const animatedProps = useAnimatedProps( () => { - const fill = interpolateColor(sharedValue.value, [0, 1], [textLight, textDark]); + const fill = interpolateColor(sharedValue.get(), [0, 1], [textLight, textDark]); return { fill, diff --git a/src/components/OpacityView.tsx b/src/components/OpacityView.tsx index f4884fd3c0f8..6c7aa26d05ba 100644 --- a/src/components/OpacityView.tsx +++ b/src/components/OpacityView.tsx @@ -44,16 +44,11 @@ function OpacityView({ }: OpacityViewProps) { const opacity = useSharedValue(1); const opacityStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, + opacity: opacity.get(), })); React.useEffect(() => { - if (shouldDim) { - // eslint-disable-next-line react-compiler/react-compiler - opacity.value = withTiming(dimmingValue, {duration: dimAnimationDuration}); - } else { - opacity.value = withTiming(1, {duration: dimAnimationDuration}); - } + opacity.set(withTiming(shouldDim ? dimmingValue : 1, {duration: dimAnimationDuration})); }, [shouldDim, dimmingValue, opacity, dimAnimationDuration]); return ( diff --git a/src/components/SpacerView.tsx b/src/components/SpacerView.tsx index bb762da1226b..3ca874e8f260 100644 --- a/src/components/SpacerView.tsx +++ b/src/components/SpacerView.tsx @@ -22,9 +22,9 @@ function SpacerView({shouldShow, style}: SpacerViewProps) { const prevShouldShow = usePrevious(shouldShow); const duration = CONST.ANIMATED_TRANSITION; const animatedStyles = useAnimatedStyle(() => ({ - borderBottomWidth: withTiming(borderBottomWidth.value, {duration}), - marginTop: withTiming(marginVertical.value, {duration}), - marginBottom: withTiming(marginVertical.value, {duration}), + borderBottomWidth: withTiming(borderBottomWidth.get(), {duration}), + marginTop: withTiming(marginVertical.get(), {duration}), + marginBottom: withTiming(marginVertical.get(), {duration}), })); React.useEffect(() => { @@ -35,9 +35,8 @@ function SpacerView({shouldShow, style}: SpacerViewProps) { marginVertical: shouldShow ? CONST.HORIZONTAL_SPACER.DEFAULT_MARGIN_VERTICAL : CONST.HORIZONTAL_SPACER.HIDDEN_MARGIN_VERTICAL, borderBottomWidth: shouldShow ? CONST.HORIZONTAL_SPACER.DEFAULT_BORDER_BOTTOM_WIDTH : CONST.HORIZONTAL_SPACER.HIDDEN_BORDER_BOTTOM_WIDTH, }; - // eslint-disable-next-line react-compiler/react-compiler - marginVertical.value = values.marginVertical; - borderBottomWidth.value = values.borderBottomWidth; + marginVertical.set(values.marginVertical); + borderBottomWidth.set(values.borderBottomWidth); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we only need to trigger when shouldShow prop is changed }, [shouldShow, prevShouldShow]); From f473d06bf18cc97b09e64821e716d6441fc29211 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Nov 2024 12:51:05 +0100 Subject: [PATCH 014/225] Fix patches --- ...ive-reanimated+3.15.1+003+fixNullViewTag.patch | 15 --------------- ...native-reanimated+3.16.1+001+hybrid-app.patch} | 4 ++-- ...imated+3.16.1+002+dontWhitelistTextProp.patch} | 0 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 patches/react-native-reanimated+3.15.1+003+fixNullViewTag.patch rename patches/{react-native-reanimated+3.15.1+001+hybrid-app.patch => react-native-reanimated+3.16.1+001+hybrid-app.patch} (91%) rename patches/{react-native-reanimated+3.15.1+002+dontWhitelistTextProp.patch => react-native-reanimated+3.16.1+002+dontWhitelistTextProp.patch} (100%) diff --git a/patches/react-native-reanimated+3.15.1+003+fixNullViewTag.patch b/patches/react-native-reanimated+3.15.1+003+fixNullViewTag.patch deleted file mode 100644 index ca982c6f8036..000000000000 --- a/patches/react-native-reanimated+3.15.1+003+fixNullViewTag.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx b/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx -index 577b4a7..c60f0f8 100644 ---- a/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx -+++ b/node_modules/react-native-reanimated/src/createAnimatedComponent/createAnimatedComponent.tsx -@@ -481,7 +481,9 @@ export function createAnimatedComponent( - ? (ref as HTMLElement) - : findNodeHandle(ref as Component); - -- this._componentViewTag = tag as number; -+ if (tag !== null) { -+ this._componentViewTag = tag as number; -+ } - - const { layout, entering, exiting, sharedTransitionTag } = this.props; - if ( diff --git a/patches/react-native-reanimated+3.15.1+001+hybrid-app.patch b/patches/react-native-reanimated+3.16.1+001+hybrid-app.patch similarity index 91% rename from patches/react-native-reanimated+3.15.1+001+hybrid-app.patch rename to patches/react-native-reanimated+3.16.1+001+hybrid-app.patch index 3b40360d5860..835df1f034a9 100644 --- a/patches/react-native-reanimated+3.15.1+001+hybrid-app.patch +++ b/patches/react-native-reanimated+3.16.1+001+hybrid-app.patch @@ -1,9 +1,9 @@ diff --git a/node_modules/react-native-reanimated/scripts/reanimated_utils.rb b/node_modules/react-native-reanimated/scripts/reanimated_utils.rb -index af0935f..ccd2a9e 100644 +index 9fc7b15..e453d84 100644 --- a/node_modules/react-native-reanimated/scripts/reanimated_utils.rb +++ b/node_modules/react-native-reanimated/scripts/reanimated_utils.rb @@ -17,7 +17,11 @@ def find_config() - :react_native_common_dir => nil, + :react_native_reanimated_dir_from_pods_root => nil, } - react_native_node_modules_dir = File.join(File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native/package.json')"`), '..') diff --git a/patches/react-native-reanimated+3.15.1+002+dontWhitelistTextProp.patch b/patches/react-native-reanimated+3.16.1+002+dontWhitelistTextProp.patch similarity index 100% rename from patches/react-native-reanimated+3.15.1+002+dontWhitelistTextProp.patch rename to patches/react-native-reanimated+3.16.1+002+dontWhitelistTextProp.patch From 4ac36b19b9d92c8a16679beb819e572f06f75354 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Nov 2024 12:52:08 +0100 Subject: [PATCH 015/225] Improve performance of rotateImage --- .../AvatarCropModal/AvatarCropModal.tsx | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index a3c4cdca78bf..44ae528c425a 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -260,20 +260,17 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Rotates the image by changing the rotation value by 90 degrees // and updating the position so the image remains in the same place after rotation const rotateImage = useCallback(() => { - rotation.set((value) => value - 90); + runOnUI(() => { + rotation.set((value) => value - 90); - const oldX = translateX.get(); - const oldY = translateY.get(); - const oldHeight = originalImageHeight.get(); - const oldWidth = originalImageWidth.get(); + const oldTranslateX = translateX.get(); + translateX.set(translateY.get()); + translateY.set(oldTranslateX * -1); - // Rotating 2d coordinates by applying the formula (x, y) → (-y, x). - translateX.set(oldY); - translateY.set(oldX * -1); - - // Since we rotated the image by 90 degrees, now width becomes height and vice versa. - originalImageHeight.set(oldWidth); - originalImageWidth.set(oldHeight); + const oldOriginalImageHeight = originalImageHeight.get(); + originalImageHeight.set(originalImageWidth.get()); + originalImageWidth.set(oldOriginalImageHeight); + })(); }, [originalImageHeight, originalImageWidth, rotation, translateX, translateY]); // Crops an image that was provided in the imageUri prop, using the current position/scale From 0667cf722d040636d7b1b1570a105e98cae15381 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Nov 2024 13:26:05 +0100 Subject: [PATCH 016/225] Continue updating reanimated usage --- .../AvatarCropModal/AvatarCropModal.tsx | 26 ++- .../CustomStatusBarAndBackground/index.tsx | 14 +- src/components/MultiGestureCanvas/index.tsx | 84 +++++----- .../MultiGestureCanvas/usePanGesture.ts | 158 ++++++++++-------- 4 files changed, 157 insertions(+), 125 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index 44ae528c425a..3ff9ccc4e3f8 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -4,7 +4,7 @@ import type {LayoutChangeEvent} from 'react-native'; import {Gesture, GestureHandlerRootView} from 'react-native-gesture-handler'; import type {GestureUpdateEvent, PanGestureChangeEventPayload, PanGestureHandlerEventPayload} from 'react-native-gesture-handler'; import ImageSize from 'react-native-image-size'; -import {interpolate, runOnUI, useSharedValue, useWorkletCallback} from 'react-native-reanimated'; +import {interpolate, runOnUI, useSharedValue} from 'react-native-reanimated'; import Button from '@components/Button'; import HeaderGap from '@components/HeaderGap'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -143,12 +143,18 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose /** * Validates that value is within the provided mix/max range. */ - const clamp = useWorkletCallback((value: number, [min, max]) => interpolate(value, [min, max], [min, max], 'clamp'), []); + const clamp = useCallback((value: number, [min, max]: [number, number]) => { + 'worklet'; + + return interpolate(value, [min, max], [min, max], 'clamp'); + }, []); /** * Returns current image size taking into account scale and rotation. */ - const getDisplayedImageSize = useWorkletCallback(() => { + const getDisplayedImageSize = useCallback(() => { + 'worklet'; + let height = imageContainerSize * scale.get(); let width = imageContainerSize * scale.get(); @@ -161,13 +167,15 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose } return {height, width}; - }, [imageContainerSize, scale]); + }, [imageContainerSize, originalImageHeight, originalImageWidth, scale]); /** * Validates the offset to prevent overflow, and updates the image offset. */ - const updateImageOffset = useWorkletCallback( + const updateImageOffset = useCallback( (offsetX: number, offsetY: number) => { + 'worklet'; + const {height, width} = getDisplayedImageSize(); const maxOffsetX = (width - imageContainerSize) / 2; const maxOffsetY = (height - imageContainerSize) / 2; @@ -176,13 +184,15 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose prevMaxOffsetX.set(maxOffsetX); prevMaxOffsetY.set(maxOffsetY); }, - [imageContainerSize, scale, clamp], + [getDisplayedImageSize, imageContainerSize, translateX, clamp, translateY, prevMaxOffsetX, prevMaxOffsetY], ); - const newScaleValue = useWorkletCallback((newSliderValue: number, containerSize: number) => { + const newScaleValue = useCallback((newSliderValue: number, containerSize: number) => { + 'worklet'; + const {MAX_SCALE, MIN_SCALE} = CONST.AVATAR_CROP_MODAL; return (newSliderValue / containerSize) * (MAX_SCALE - MIN_SCALE) + MIN_SCALE; - }); + }, []); /** * Calculates new x & y image translate value on image panning diff --git a/src/components/CustomStatusBarAndBackground/index.tsx b/src/components/CustomStatusBarAndBackground/index.tsx index ec52f07d211c..ac1fc77dff96 100644 --- a/src/components/CustomStatusBarAndBackground/index.tsx +++ b/src/components/CustomStatusBarAndBackground/index.tsx @@ -44,14 +44,14 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack const statusBarAnimation = useSharedValue(0); useAnimatedReaction( - () => statusBarAnimation.value, + () => statusBarAnimation.get(), (current, previous) => { // Do not run if either of the animated value is null // or previous animated value is greater than or equal to the current one if (previous === null || current === null || current <= previous) { return; } - const backgroundColor = interpolateColor(statusBarAnimation.value, [0, 1], [prevStatusBarBackgroundColor.value, statusBarBackgroundColor.value]); + const backgroundColor = interpolateColor(statusBarAnimation.get(), [0, 1], [prevStatusBarBackgroundColor.get(), statusBarBackgroundColor.get()]); runOnJS(updateStatusBarAppearance)({backgroundColor}); }, ); @@ -92,8 +92,8 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack currentScreenBackgroundColor = backgroundColorFromRoute || pageTheme.backgroundColor; } - prevStatusBarBackgroundColor.value = statusBarBackgroundColor.value; - statusBarBackgroundColor.value = currentScreenBackgroundColor; + prevStatusBarBackgroundColor.set(statusBarBackgroundColor.get()); + statusBarBackgroundColor.set(currentScreenBackgroundColor); const callUpdateStatusBarAppearance = () => { updateStatusBarAppearance({statusBarStyle: newStatusBarStyle}); @@ -101,8 +101,8 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack }; const callUpdateStatusBarBackgroundColor = () => { - statusBarAnimation.value = 0; - statusBarAnimation.value = withDelay(300, withTiming(1)); + statusBarAnimation.set(0); + statusBarAnimation.set(withDelay(300, withTiming(1))); }; // Don't update the status bar style if it's the same as the current one, to prevent flashing. @@ -121,7 +121,7 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack callUpdateStatusBarAppearance(); } - if (currentScreenBackgroundColor !== theme.appBG || prevStatusBarBackgroundColor.value !== theme.appBG) { + if (currentScreenBackgroundColor !== theme.appBG || prevStatusBarBackgroundColor.get() !== theme.appBG) { callUpdateStatusBarBackgroundColor(); } }, diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index ff9566839d59..fda3ee9bea60 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -1,12 +1,12 @@ import type {ForwardedRef} from 'react'; -import React, {useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import type {GestureType} from 'react-native-gesture-handler'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import type {GestureRef} from 'react-native-gesture-handler/lib/typescript/handlers/gestures/gesture'; import type PagerView from 'react-native-pager-view'; import type {SharedValue} from 'react-native-reanimated'; -import Animated, {cancelAnimation, runOnUI, useAnimatedStyle, useDerivedValue, useSharedValue, useWorkletCallback, withSpring} from 'react-native-reanimated'; +import Animated, {cancelAnimation, runOnUI, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring} from 'react-native-reanimated'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -92,7 +92,7 @@ function MultiGestureCanvas({ // Adding together zoom scale and the initial scale to fit the content into the canvas // Using the minimum content scale, so that the image is not bigger than the canvas // and not smaller than needed to fit - const totalScale = useDerivedValue(() => zoomScale.value * minContentScale, [minContentScale]); + const totalScale = useDerivedValue(() => zoomScale.get() * minContentScale, [minContentScale]); const panTranslateX = useSharedValue(0); const panTranslateY = useSharedValue(0); @@ -110,44 +110,50 @@ function MultiGestureCanvas({ /** * Stops any currently running decay animation from panning */ - const stopAnimation = useWorkletCallback(() => { + const stopAnimation = useCallback(() => { + 'worklet'; + cancelAnimation(offsetX); cancelAnimation(offsetY); - }); + }, [offsetX, offsetY]); /** * Resets the canvas to the initial state and animates back smoothly */ - const reset = useWorkletCallback((animated: boolean, callback?: () => void) => { - stopAnimation(); - - // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = 0; - offsetY.value = 0; - pinchScale.value = 1; - - if (animated) { - panTranslateX.value = withSpring(0, SPRING_CONFIG); - panTranslateY.value = withSpring(0, SPRING_CONFIG); - pinchTranslateX.value = withSpring(0, SPRING_CONFIG); - pinchTranslateY.value = withSpring(0, SPRING_CONFIG); - zoomScale.value = withSpring(1, SPRING_CONFIG, callback); - - return; - } - - panTranslateX.value = 0; - panTranslateY.value = 0; - pinchTranslateX.value = 0; - pinchTranslateY.value = 0; - zoomScale.value = 1; - - if (callback === undefined) { - return; - } - - callback(); - }); + const reset = useCallback( + (animated: boolean, callback?: () => void) => { + 'worklet'; + + stopAnimation(); + + offsetX.set(0); + offsetY.set(0); + pinchScale.set(1); + + if (animated) { + panTranslateX.set(withSpring(0, SPRING_CONFIG)); + panTranslateY.set(withSpring(0, SPRING_CONFIG)); + pinchTranslateX.set(withSpring(0, SPRING_CONFIG)); + pinchTranslateY.set(withSpring(0, SPRING_CONFIG)); + zoomScale.set(withSpring(1, SPRING_CONFIG, callback)); + + return; + } + + panTranslateX.set(0); + panTranslateY.set(0); + pinchTranslateX.set(0); + pinchTranslateY.set(0); + zoomScale.set(1); + + if (callback === undefined) { + return; + } + + callback(); + }, + [offsetX, offsetY, panTranslateX, panTranslateY, pinchScale, pinchTranslateX, pinchTranslateY, stopAnimation, zoomScale], + ); const {singleTapGesture: baseSingleTapGesture, doubleTapGesture} = useTapGestures({ canvasSize, @@ -164,6 +170,7 @@ function MultiGestureCanvas({ onTap, shouldDisableTransformationGestures, }); + // eslint-disable-next-line react-compiler/react-compiler const singleTapGesture = baseSingleTapGesture.requireExternalGestureToFail(doubleTapGesture, panGestureRef); const panGestureSimultaneousList = useMemo( @@ -186,6 +193,7 @@ function MultiGestureCanvas({ onSwipeDown, }) .simultaneousWithExternalGesture(...panGestureSimultaneousList) + // eslint-disable-next-line react-compiler/react-compiler .withRef(panGestureRef); const pinchGesture = usePinchGesture({ @@ -217,8 +225,8 @@ function MultiGestureCanvas({ // Animate the x and y position of the content within the canvas based on all of the gestures const animatedStyles = useAnimatedStyle(() => { - const x = pinchTranslateX.value + panTranslateX.value + offsetX.value; - const y = pinchTranslateY.value + panTranslateY.value + offsetY.value; + const x = pinchTranslateX.get() + panTranslateX.get() + offsetX.get(); + const y = pinchTranslateY.get() + panTranslateY.get() + offsetY.get(); return { transform: [ @@ -228,7 +236,7 @@ function MultiGestureCanvas({ { translateY: y, }, - {scale: totalScale.value}, + {scale: totalScale.get()}, ], // Hide the image if the size is not ready yet opacity: contentSizeProp?.width ? 1 : 0, diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index b31e310055ae..bd29a18cc46c 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -1,8 +1,9 @@ /* eslint-disable no-param-reassign */ +import {useCallback} from 'react'; import {Dimensions} from 'react-native'; import type {PanGesture} from 'react-native-gesture-handler'; import {Gesture} from 'react-native-gesture-handler'; -import {runOnJS, useDerivedValue, useSharedValue, useWorkletCallback, withDecay, withSpring} from 'react-native-reanimated'; +import {runOnJS, useDerivedValue, useSharedValue, withDecay, withSpring} from 'react-native-reanimated'; import * as Browser from '@libs/Browser'; import {SPRING_CONFIG} from './constants'; import type {MultiGestureCanvasVariables} from './types'; @@ -47,8 +48,8 @@ const usePanGesture = ({ onSwipeDown, }: UsePanGestureProps): PanGesture => { // The content size after fitting it to the canvas and zooming - const zoomedContentWidth = useDerivedValue(() => contentSize.width * totalScale.value, [contentSize.width]); - const zoomedContentHeight = useDerivedValue(() => contentSize.height * totalScale.value, [contentSize.height]); + const zoomedContentWidth = useDerivedValue(() => contentSize.width * totalScale.get(), [contentSize.width]); + const zoomedContentHeight = useDerivedValue(() => contentSize.height * totalScale.get(), [contentSize.height]); // Used to track previous touch position for the "swipe down to close" gesture const previousTouch = useSharedValue<{x: number; y: number} | null>(null); @@ -61,140 +62,153 @@ const usePanGesture = ({ const isMobileBrowser = Browser.isMobile(); // Disable "swipe down to close" gesture when content is bigger than the canvas - const enableSwipeDownToClose = useDerivedValue(() => canvasSize.height < zoomedContentHeight.value, [canvasSize.height]); + const enableSwipeDownToClose = useDerivedValue(() => canvasSize.height < zoomedContentHeight.get(), [canvasSize.height]); // Calculates bounds of the scaled content // Can we pan left/right/up/down // Can be used to limit gesture or implementing tension effect - const getBounds = useWorkletCallback(() => { + const getBounds = useCallback(() => { + 'worklet'; + let horizontalBoundary = 0; let verticalBoundary = 0; - if (canvasSize.width < zoomedContentWidth.value) { - horizontalBoundary = Math.abs(canvasSize.width - zoomedContentWidth.value) / 2; + if (canvasSize.width < zoomedContentWidth.get()) { + horizontalBoundary = Math.abs(canvasSize.width - zoomedContentWidth.get()) / 2; } - if (canvasSize.height < zoomedContentHeight.value) { - verticalBoundary = Math.abs(zoomedContentHeight.value - canvasSize.height) / 2; + if (canvasSize.height < zoomedContentHeight.get()) { + verticalBoundary = Math.abs(zoomedContentHeight.get() - canvasSize.height) / 2; } const horizontalBoundaries = {min: -horizontalBoundary, max: horizontalBoundary}; const verticalBoundaries = {min: -verticalBoundary, max: verticalBoundary}; const clampedOffset = { - x: MultiGestureCanvasUtils.clamp(offsetX.value, horizontalBoundaries.min, horizontalBoundaries.max), - y: MultiGestureCanvasUtils.clamp(offsetY.value, verticalBoundaries.min, verticalBoundaries.max), + x: MultiGestureCanvasUtils.clamp(offsetX.get(), horizontalBoundaries.min, horizontalBoundaries.max), + y: MultiGestureCanvasUtils.clamp(offsetY.get(), verticalBoundaries.min, verticalBoundaries.max), }; // If the horizontal/vertical offset is the same after clamping to the min/max boundaries, the content is within the boundaries - const isInHoriztontalBoundary = clampedOffset.x === offsetX.value; - const isInVerticalBoundary = clampedOffset.y === offsetY.value; + const isInHorizontalBoundary = clampedOffset.x === offsetX.get(); + const isInVerticalBoundary = clampedOffset.y === offsetY.get(); return { horizontalBoundaries, verticalBoundaries, clampedOffset, - isInHoriztontalBoundary, + isInHorizontalBoundary, isInVerticalBoundary, }; - }, [canvasSize.width, canvasSize.height]); + }, [canvasSize.width, canvasSize.height, zoomedContentWidth, zoomedContentHeight, offsetX, offsetY]); // We want to smoothly decay/end the gesture by phasing out the pan animation // In case the content is outside of the boundaries of the canvas, // we need to move the content back into the boundaries - const finishPanGesture = useWorkletCallback(() => { + const finishPanGesture = useCallback(() => { + 'worklet'; + // If the content is centered within the canvas, we don't need to run any animations - if (offsetX.value === 0 && offsetY.value === 0 && panTranslateX.value === 0 && panTranslateY.value === 0) { + if (offsetX.get() === 0 && offsetY.get() === 0 && panTranslateX.get() === 0 && panTranslateY.get() === 0) { return; } - const {clampedOffset, isInHoriztontalBoundary, isInVerticalBoundary, horizontalBoundaries, verticalBoundaries} = getBounds(); + const {clampedOffset, isInHorizontalBoundary, isInVerticalBoundary, horizontalBoundaries, verticalBoundaries} = getBounds(); // If the content is within the horizontal/vertical boundaries of the canvas, we can smoothly phase out the animation // If not, we need to snap back to the boundaries - if (isInHoriztontalBoundary) { + if (isInHorizontalBoundary) { // If the (absolute) velocity is 0, we don't need to run an animation - if (Math.abs(panVelocityX.value) !== 0) { + if (Math.abs(panVelocityX.get()) !== 0) { // Phase out the pan animation // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = withDecay({ - velocity: panVelocityX.value, - clamp: [horizontalBoundaries.min, horizontalBoundaries.max], - deceleration: PAN_DECAY_DECELARATION, - rubberBandEffect: false, - }); + offsetX.set( + withDecay({ + velocity: panVelocityX.get(), + clamp: [horizontalBoundaries.min, horizontalBoundaries.max], + deceleration: PAN_DECAY_DECELARATION, + rubberBandEffect: false, + }), + ); } } else { // Animated back to the boundary - offsetX.value = withSpring(clampedOffset.x, SPRING_CONFIG); + offsetX.set(withSpring(clampedOffset.x, SPRING_CONFIG)); } if (isInVerticalBoundary) { // If the (absolute) velocity is 0, we don't need to run an animation - if (Math.abs(panVelocityY.value) !== 0) { + if (Math.abs(panVelocityY.get()) !== 0) { // Phase out the pan animation - offsetY.value = withDecay({ - velocity: panVelocityY.value, - clamp: [verticalBoundaries.min, verticalBoundaries.max], - deceleration: PAN_DECAY_DECELARATION, - }); + offsetY.set( + withDecay({ + velocity: panVelocityY.get(), + clamp: [verticalBoundaries.min, verticalBoundaries.max], + deceleration: PAN_DECAY_DECELARATION, + }), + ); } } else { - const finalTranslateY = offsetY.value + panVelocityY.value * 0.2; - - if (finalTranslateY > SNAP_POINT && zoomScale.value <= 1) { - offsetY.value = withSpring(SNAP_POINT_HIDDEN, SPRING_CONFIG, () => { - isSwipingDownToClose.value = false; - - if (onSwipeDown) { - runOnJS(onSwipeDown)(); - } - }); + const finalTranslateY = offsetY.get() + panVelocityY.get() * 0.2; + + if (finalTranslateY > SNAP_POINT && zoomScale.get() <= 1) { + offsetY.set( + withSpring(SNAP_POINT_HIDDEN, SPRING_CONFIG, () => { + isSwipingDownToClose.set(false); + + if (onSwipeDown) { + runOnJS(onSwipeDown)(); + } + }), + ); } else { // Animated back to the boundary - offsetY.value = withSpring(clampedOffset.y, SPRING_CONFIG, () => { - isSwipingDownToClose.value = false; - }); + offsetY.set( + withSpring(clampedOffset.y, SPRING_CONFIG, () => { + isSwipingDownToClose.set(false); + }), + ); } } // Reset velocity variables after we finished the pan gesture - panVelocityX.value = 0; - panVelocityY.value = 0; - }); + panVelocityX.set(0); + panVelocityY.set(0); + }, [getBounds, isSwipingDownToClose, offsetX, offsetY, onSwipeDown, panTranslateX, panTranslateY, panVelocityX, panVelocityY, zoomScale]); const panGesture = Gesture.Pan() .manualActivation(true) .averageTouches(true) .onTouchesUp(() => { - previousTouch.value = null; + previousTouch.set(null); }) .onTouchesMove((evt, state) => { // We only allow panning when the content is zoomed in - if (zoomScale.value > 1 && !shouldDisableTransformationGestures.value) { + if (zoomScale.get() > 1 && !shouldDisableTransformationGestures.get()) { state.activate(); } // TODO: this needs tuning to work properly - if (!shouldDisableTransformationGestures.value && zoomScale.value === 1 && previousTouch.value !== null) { - const velocityX = Math.abs((evt.allTouches.at(0)?.x ?? 0) - previousTouch.value.x); - const velocityY = (evt.allTouches.at(0)?.y ?? 0) - previousTouch.value.y; + const previousTouchValue = previousTouch.get(); + if (!shouldDisableTransformationGestures.get() && zoomScale.get() === 1 && previousTouchValue !== null) { + const velocityX = Math.abs((evt.allTouches.at(0)?.x ?? 0) - previousTouchValue.x); + const velocityY = (evt.allTouches.at(0)?.y ?? 0) - previousTouchValue.y; if (Math.abs(velocityY) > velocityX && velocityY > 20) { state.activate(); - isSwipingDownToClose.value = true; - previousTouch.value = null; + isSwipingDownToClose.set(true); + previousTouch.set(null); return; } } - if (previousTouch.value === null) { - previousTouch.value = { + if (previousTouchValue === null) { + previousTouch.set({ x: evt.allTouches.at(0)?.x ?? 0, y: evt.allTouches.at(0)?.y ?? 0, - }; + }); } }) .onStart(() => { @@ -207,31 +221,31 @@ const usePanGesture = ({ return; } - panVelocityX.value = evt.velocityX; - panVelocityY.value = evt.velocityY; + panVelocityX.set(evt.velocityX); + panVelocityY.set(evt.velocityY); - if (!isSwipingDownToClose.value) { - if (!isMobileBrowser || (isMobileBrowser && zoomScale.value !== 1)) { - panTranslateX.value += evt.changeX; + if (!isSwipingDownToClose.get()) { + if (!isMobileBrowser || (isMobileBrowser && zoomScale.get() !== 1)) { + panTranslateX.set((value) => value + evt.changeX); } } - if (enableSwipeDownToClose.value || isSwipingDownToClose.value) { - panTranslateY.value += evt.changeY; + if (enableSwipeDownToClose.get() || isSwipingDownToClose.get()) { + panTranslateY.set((value) => value + evt.changeY); } }) .onEnd(() => { // Add pan translation to total offset and reset gesture variables - offsetX.value += panTranslateX.value; - offsetY.value += panTranslateY.value; + offsetX.set((value) => value + panTranslateX.get()); + offsetY.set((value) => value + panTranslateY.get()); // Reset pan gesture variables - panTranslateX.value = 0; - panTranslateY.value = 0; - previousTouch.value = null; + panTranslateX.set(0); + panTranslateY.set(0); + previousTouch.set(null); // If we are swiping (in the pager), we don't want to return to boundaries - if (shouldDisableTransformationGestures.value) { + if (shouldDisableTransformationGestures.get()) { return; } From 73c358f73962f392493c082b880579523c4b0ff7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Nov 2024 14:40:45 +0100 Subject: [PATCH 017/225] Finish migrating to new reanimated --- .../MultiGestureCanvas/usePinchGesture.ts | 88 ++++++++++--------- .../MultiGestureCanvas/useTapGestures.ts | 23 +++-- .../ReportActionItem/ReportPreview.tsx | 19 ++-- .../AnimatedSettlementButton.tsx | 44 +++++----- .../SplashScreenHider/index.native.tsx | 31 ++++--- .../VideoPlayer/BaseVideoPlayer.tsx | 7 +- .../VideoPlayerControls/ProgressBar/index.tsx | 11 ++- .../VolumeButton/index.tsx | 15 ++-- .../VideoPlayerContexts/VolumeContext.tsx | 7 +- src/hooks/useAnimatedHighlightStyle/index.ts | 35 ++++---- src/pages/Search/SearchPageBottomTab.tsx | 15 ++-- .../report/AnimatedEmptyStateBackground.tsx | 9 +- .../home/report/FloatingMessageCounter.tsx | 6 +- .../ComposerWithSuggestions.tsx | 13 ++- .../ReportActionCompose.tsx | 7 +- .../report/ReportActionItemMessageEdit.tsx | 14 ++- .../step/IOURequestStepScan/index.native.tsx | 13 ++- .../BackgroundImage/index.ios.tsx | 13 +-- 18 files changed, 182 insertions(+), 188 deletions(-) diff --git a/src/components/MultiGestureCanvas/usePinchGesture.ts b/src/components/MultiGestureCanvas/usePinchGesture.ts index 46a5e28e5732..7c517ad2f30b 100644 --- a/src/components/MultiGestureCanvas/usePinchGesture.ts +++ b/src/components/MultiGestureCanvas/usePinchGesture.ts @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import {useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import type {PinchGesture} from 'react-native-gesture-handler'; import {Gesture} from 'react-native-gesture-handler'; -import {runOnJS, useAnimatedReaction, useSharedValue, useWorkletCallback, withSpring} from 'react-native-reanimated'; +import {runOnJS, useAnimatedReaction, useSharedValue, withSpring} from 'react-native-reanimated'; import {SPRING_CONFIG, ZOOM_RANGE_BOUNCE_FACTORS} from './constants'; import type {MultiGestureCanvasVariables} from './types'; @@ -61,16 +61,16 @@ const usePinchGesture = ({ return; } - runOnJS(onScaleChanged)(zoomScale.value); + runOnJS(onScaleChanged)(zoomScale.get()); }; // Update the total (pinch) translation based on the regular pinch + bounce useAnimatedReaction( - () => [pinchTranslateX.value, pinchTranslateY.value, pinchBounceTranslateX.value, pinchBounceTranslateY.value], + () => [pinchTranslateX.get(), pinchTranslateY.get(), pinchBounceTranslateX.get(), pinchBounceTranslateY.get()], ([translateX, translateY, bounceX, bounceY]) => { // eslint-disable-next-line react-compiler/react-compiler - totalPinchTranslateX.value = translateX + bounceX; - totalPinchTranslateY.value = translateY + bounceY; + totalPinchTranslateX.set(translateX + bounceX); + totalPinchTranslateY.set(translateY + bounceY); }, ); @@ -78,12 +78,16 @@ const usePinchGesture = ({ * Calculates the adjusted focal point of the pinch gesture, * based on the canvas size and the current offset */ - const getAdjustedFocal = useWorkletCallback( - (focalX: number, focalY: number) => ({ - x: focalX - (canvasSize.width / 2 + offsetX.value), - y: focalY - (canvasSize.height / 2 + offsetY.value), - }), - [canvasSize.width, canvasSize.height], + const getAdjustedFocal = useCallback( + (focalX: number, focalY: number) => { + 'worklet'; + + return { + x: focalX - (canvasSize.width / 2 + offsetX.get()), + y: focalY - (canvasSize.height / 2 + offsetY.get()), + }; + }, + [canvasSize.width, canvasSize.height, offsetX, offsetY], ); // The pinch gesture is disabled when we release one of the fingers @@ -101,7 +105,7 @@ const usePinchGesture = ({ // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { // We don't want to activate pinch gesture when we are swiping in the pager - if (!shouldDisableTransformationGestures.value) { + if (!shouldDisableTransformationGestures.get()) { return; } @@ -112,8 +116,8 @@ const usePinchGesture = ({ // Set the origin focal point of the pinch gesture at the start of the gesture const adjustedFocal = getAdjustedFocal(evt.focalX, evt.focalY); - pinchOrigin.x.value = adjustedFocal.x; - pinchOrigin.y.value = adjustedFocal.y; + pinchOrigin.x.set(adjustedFocal.x); + pinchOrigin.y.set(adjustedFocal.y); }) .onChange((evt) => { // Disable the pinch gesture if one finger is released, @@ -123,58 +127,58 @@ const usePinchGesture = ({ return; } - const newZoomScale = pinchScale.value * evt.scale; - + const newZoomScale = pinchScale.get() * evt.scale; + const zoomScaleValue = zoomScale.get(); // Limit the zoom scale to zoom range including bounce range - if (zoomScale.value >= zoomRange.min * ZOOM_RANGE_BOUNCE_FACTORS.min && zoomScale.value <= zoomRange.max * ZOOM_RANGE_BOUNCE_FACTORS.max) { - zoomScale.value = newZoomScale; - currentPinchScale.value = evt.scale; + if (zoomScaleValue >= zoomRange.min * ZOOM_RANGE_BOUNCE_FACTORS.min && zoomScaleValue <= zoomRange.max * ZOOM_RANGE_BOUNCE_FACTORS.max) { + zoomScale.set(newZoomScale); + currentPinchScale.set(evt.scale); triggerScaleChangedEvent(); } // Calculate new pinch translation const adjustedFocal = getAdjustedFocal(evt.focalX, evt.focalY); - const newPinchTranslateX = adjustedFocal.x + currentPinchScale.value * pinchOrigin.x.value * -1; - const newPinchTranslateY = adjustedFocal.y + currentPinchScale.value * pinchOrigin.y.value * -1; + const newPinchTranslateX = adjustedFocal.x + currentPinchScale.get() * pinchOrigin.x.get() * -1; + const newPinchTranslateY = adjustedFocal.y + currentPinchScale.get() * pinchOrigin.y.get() * -1; // If the zoom scale is within the zoom range, we perform the regular pinch translation // Otherwise it means that we are "overzoomed" or "underzoomed", so we need to bounce back - if (zoomScale.value >= zoomRange.min && zoomScale.value <= zoomRange.max) { - pinchTranslateX.value = newPinchTranslateX; - pinchTranslateY.value = newPinchTranslateY; + if (zoomScaleValue >= zoomRange.min && zoomScaleValue <= zoomRange.max) { + pinchTranslateX.set(newPinchTranslateX); + pinchTranslateY.set(newPinchTranslateY); } else { // Store x and y translation that is produced while bouncing // so we can revert the bounce once pinch gesture is released - pinchBounceTranslateX.value = newPinchTranslateX - pinchTranslateX.value; - pinchBounceTranslateY.value = newPinchTranslateY - pinchTranslateY.value; + pinchBounceTranslateX.set(newPinchTranslateX - pinchTranslateX.get()); + pinchBounceTranslateY.set(newPinchTranslateY - pinchTranslateY.get()); } }) .onEnd(() => { // Add pinch translation to total offset and reset gesture variables - offsetX.value += pinchTranslateX.value; - offsetY.value += pinchTranslateY.value; - pinchTranslateX.value = 0; - pinchTranslateY.value = 0; - currentPinchScale.value = 1; + offsetX.set((value) => value + pinchTranslateX.get()); + offsetY.set((value) => value + pinchTranslateY.get()); + pinchTranslateX.set(0); + pinchTranslateY.set(0); + currentPinchScale.set(1); // If the content was "overzoomed" or "underzoomed", we need to bounce back with an animation - if (pinchBounceTranslateX.value !== 0 || pinchBounceTranslateY.value !== 0) { - pinchBounceTranslateX.value = withSpring(0, SPRING_CONFIG); - pinchBounceTranslateY.value = withSpring(0, SPRING_CONFIG); + if (pinchBounceTranslateX.get() !== 0 || pinchBounceTranslateY.get() !== 0) { + pinchBounceTranslateX.set(withSpring(0, SPRING_CONFIG)); + pinchBounceTranslateY.set(withSpring(0, SPRING_CONFIG)); } - if (zoomScale.value < zoomRange.min) { + if (zoomScale.get() < zoomRange.min) { // If the zoom scale is less than the minimum zoom scale, we need to set the zoom scale to the minimum - pinchScale.value = zoomRange.min; - zoomScale.value = withSpring(zoomRange.min, SPRING_CONFIG, triggerScaleChangedEvent); - } else if (zoomScale.value > zoomRange.max) { + pinchScale.set(zoomRange.min); + zoomScale.set(withSpring(zoomRange.min, SPRING_CONFIG, triggerScaleChangedEvent)); + } else if (zoomScale.get() > zoomRange.max) { // If the zoom scale is higher than the maximum zoom scale, we need to set the zoom scale to the maximum - pinchScale.value = zoomRange.max; - zoomScale.value = withSpring(zoomRange.max, SPRING_CONFIG, triggerScaleChangedEvent); + pinchScale.set(zoomRange.max); + zoomScale.set(withSpring(zoomRange.max, SPRING_CONFIG, triggerScaleChangedEvent)); } else { // Otherwise, we just update the pinch scale offset - pinchScale.value = zoomScale.value; + pinchScale.set(zoomScale.get()); triggerScaleChangedEvent(); } }); diff --git a/src/components/MultiGestureCanvas/useTapGestures.ts b/src/components/MultiGestureCanvas/useTapGestures.ts index e4bb02bd5d34..a918310d2862 100644 --- a/src/components/MultiGestureCanvas/useTapGestures.ts +++ b/src/components/MultiGestureCanvas/useTapGestures.ts @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import {useMemo} from 'react'; +import {useCallback, useMemo} from 'react'; import type {TapGesture} from 'react-native-gesture-handler'; import {Gesture} from 'react-native-gesture-handler'; -import {runOnJS, useWorkletCallback, withSpring} from 'react-native-reanimated'; +import {runOnJS, withSpring} from 'react-native-reanimated'; import {DOUBLE_TAP_SCALE, SPRING_CONFIG} from './constants'; import type {MultiGestureCanvasVariables} from './types'; import * as MultiGestureCanvasUtils from './utils'; @@ -46,7 +46,7 @@ const useTapGestures = ({ // On double tap the content should be zoomed to fill, but at least zoomed by DOUBLE_TAP_SCALE const doubleTapScale = useMemo(() => Math.max(DOUBLE_TAP_SCALE, maxContentScale / minContentScale), [maxContentScale, minContentScale]); - const zoomToCoordinates = useWorkletCallback( + const zoomToCoordinates = useCallback( (focalX: number, focalY: number, callback: () => void) => { 'worklet'; @@ -111,19 +111,18 @@ const useTapGestures = ({ offsetAfterZooming.y = 0; } - // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = withSpring(offsetAfterZooming.x, SPRING_CONFIG); - offsetY.value = withSpring(offsetAfterZooming.y, SPRING_CONFIG); - zoomScale.value = withSpring(doubleTapScale, SPRING_CONFIG, callback); - pinchScale.value = doubleTapScale; + offsetX.set(withSpring(offsetAfterZooming.x, SPRING_CONFIG)); + offsetY.set(withSpring(offsetAfterZooming.y, SPRING_CONFIG)); + zoomScale.set(withSpring(doubleTapScale, SPRING_CONFIG, callback)); + pinchScale.set(doubleTapScale); }, - [scaledContentWidth, scaledContentHeight, canvasSize, doubleTapScale], + [stopAnimation, canvasSize.width, canvasSize.height, scaledContentWidth, scaledContentHeight, doubleTapScale, offsetX, offsetY, zoomScale, pinchScale], ); const doubleTapGesture = Gesture.Tap() // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { - if (!shouldDisableTransformationGestures.value) { + if (!shouldDisableTransformationGestures.get()) { return; } @@ -137,13 +136,13 @@ const useTapGestures = ({ 'worklet'; if (onScaleChanged != null) { - runOnJS(onScaleChanged)(zoomScale.value); + runOnJS(onScaleChanged)(zoomScale.get()); } }; // If the content is already zoomed, we want to reset the zoom, // otherwise we want to zoom in - if (zoomScale.value > 1) { + if (zoomScale.get() > 1) { reset(true, triggerScaleChangedEvent); } else { zoomToCoordinates(evt.x, evt.y, triggerScaleChangedEvent); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 9067f1abb11a..684e922e2dc6 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -135,7 +135,7 @@ function ReportPreview({ const iouSettled = ReportUtils.isSettled(iouReportID) || action?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED; const previewMessageOpacity = useSharedValue(1); const previewMessageStyle = useAnimatedStyle(() => ({ - opacity: previewMessageOpacity.value, + opacity: previewMessageOpacity.get(), })); const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); @@ -425,11 +425,11 @@ function ReportPreview({ return; } - // eslint-disable-next-line react-compiler/react-compiler - previewMessageOpacity.value = withTiming(0.75, {duration: CONST.ANIMATION_PAID_DURATION / 2}, () => { - // eslint-disable-next-line react-compiler/react-compiler - previewMessageOpacity.value = withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION / 2}); - }); + previewMessageOpacity.set( + withTiming(0.75, {duration: CONST.ANIMATION_PAID_DURATION / 2}, () => { + previewMessageOpacity.set(withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION / 2})); + }), + ); // We only want to animate the text when the text changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [previewMessage, previewMessageOpacity]); @@ -439,12 +439,7 @@ function ReportPreview({ return; } - if (isPaidAnimationRunning) { - // eslint-disable-next-line react-compiler/react-compiler - checkMarkScale.value = withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})); - } else { - checkMarkScale.value = 1; - } + checkMarkScale.set(isPaidAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1); }, [isPaidAnimationRunning, iouSettled, checkMarkScale]); return ( diff --git a/src/components/SettlementButton/AnimatedSettlementButton.tsx b/src/components/SettlementButton/AnimatedSettlementButton.tsx index 5de528d741a2..65c2fd2f493b 100644 --- a/src/components/SettlementButton/AnimatedSettlementButton.tsx +++ b/src/components/SettlementButton/AnimatedSettlementButton.tsx @@ -23,20 +23,20 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is const height = useSharedValue(variables.componentSizeNormal); const buttonMarginTop = useSharedValue(styles.expenseAndReportPreviewTextButtonContainer.gap); const buttonStyles = useAnimatedStyle(() => ({ - transform: [{scale: buttonScale.value}], - opacity: buttonOpacity.value, + transform: [{scale: buttonScale.get()}], + opacity: buttonOpacity.get(), })); const paymentCompleteTextStyles = useAnimatedStyle(() => ({ - transform: [{scale: paymentCompleteTextScale.value}], - opacity: paymentCompleteTextOpacity.value, + transform: [{scale: paymentCompleteTextScale.get()}], + opacity: paymentCompleteTextOpacity.get(), position: 'absolute', alignSelf: 'center', })); const containerStyles = useAnimatedStyle(() => ({ - height: height.value, + height: height.get(), justifyContent: 'center', overflow: 'hidden', - marginTop: buttonMarginTop.value, + marginTop: buttonMarginTop.get(), })); const buttonDisabledStyle = isPaidAnimationRunning ? { @@ -46,13 +46,12 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is : undefined; const resetAnimation = useCallback(() => { - // eslint-disable-next-line react-compiler/react-compiler - buttonScale.value = 1; - buttonOpacity.value = 1; - paymentCompleteTextScale.value = 0; - paymentCompleteTextOpacity.value = 1; - height.value = variables.componentSizeNormal; - buttonMarginTop.value = styles.expenseAndReportPreviewTextButtonContainer.gap; + buttonScale.set(1); + buttonOpacity.set(1); + paymentCompleteTextScale.set(0); + paymentCompleteTextOpacity.set(1); + height.set(variables.componentSizeNormal); + buttonMarginTop.set(styles.expenseAndReportPreviewTextButtonContainer.gap); }, [buttonScale, buttonOpacity, paymentCompleteTextScale, paymentCompleteTextOpacity, height, buttonMarginTop, styles.expenseAndReportPreviewTextButtonContainer.gap]); useEffect(() => { @@ -60,19 +59,20 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is resetAnimation(); return; } - // eslint-disable-next-line react-compiler/react-compiler - buttonScale.value = withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}); - buttonOpacity.value = withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}); - paymentCompleteTextScale.value = withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION}); + buttonScale.set(withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); + buttonOpacity.set(withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); + paymentCompleteTextScale.set(withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION})); // Wait for the above animation + 1s delay before hiding the component const totalDelay = CONST.ANIMATION_PAID_DURATION + CONST.ANIMATION_PAID_BUTTON_HIDE_DELAY; - height.value = withDelay( - totalDelay, - withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()), + height.set( + withDelay( + totalDelay, + withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()), + ), ); - buttonMarginTop.value = withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); - paymentCompleteTextOpacity.value = withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); + buttonMarginTop.set(withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}))); + paymentCompleteTextOpacity.set(withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}))); }, [isPaidAnimationRunning, onAnimationFinish, buttonOpacity, buttonScale, height, paymentCompleteTextOpacity, paymentCompleteTextScale, buttonMarginTop, resetAnimation]); return ( diff --git a/src/components/SplashScreenHider/index.native.tsx b/src/components/SplashScreenHider/index.native.tsx index 7c579519c926..d41770fb7f52 100644 --- a/src/components/SplashScreenHider/index.native.tsx +++ b/src/components/SplashScreenHider/index.native.tsx @@ -17,10 +17,10 @@ function SplashScreenHider({onHide = () => {}}: SplashScreenHiderProps): SplashS const scale = useSharedValue(1); const opacityStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, + opacity: opacity.get(), })); const scaleStyle = useAnimatedStyle(() => ({ - transform: [{scale: scale.value}], + transform: [{scale: scale.get()}], })); const hideHasBeenCalled = useRef(false); @@ -34,19 +34,22 @@ function SplashScreenHider({onHide = () => {}}: SplashScreenHiderProps): SplashS hideHasBeenCalled.current = true; BootSplash.hide().then(() => { - // eslint-disable-next-line react-compiler/react-compiler - scale.value = withTiming(0, { - duration: 200, - easing: Easing.back(2), - }); + scale.set( + withTiming(0, { + duration: 200, + easing: Easing.back(2), + }), + ); - opacity.value = withTiming( - 0, - { - duration: 250, - easing: Easing.out(Easing.ease), - }, - () => runOnJS(onHide)(), + opacity.set( + withTiming( + 0, + { + duration: 250, + easing: Easing.out(Easing.ease), + }, + () => runOnJS(onHide)(), + ), ); }); }, [opacity, scale, onHide]); diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 012537b75108..7fd26a177b0c 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -78,7 +78,7 @@ function BaseVideoPlayer({ const [controlStatusState, setControlStatusState] = useState(controlsStatus); const controlsOpacity = useSharedValue(1); const controlsAnimatedStyle = useAnimatedStyle(() => ({ - opacity: controlsOpacity.value, + opacity: controlsOpacity.get(), })); const videoPlayerRef = useRef(null); @@ -106,8 +106,7 @@ function BaseVideoPlayer({ }, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url, videoResumeTryNumberRef]); const hideControl = useCallback(() => { - // eslint-disable-next-line react-compiler/react-compiler - controlsOpacity.value = withTiming(0, {duration: 500}, () => runOnJS(setControlStatusState)(CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE)); + controlsOpacity.set(withTiming(0, {duration: 500}, () => runOnJS(setControlStatusState)(CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE))); }, [controlsOpacity]); const debouncedHideControl = useMemo(() => debounce(hideControl, 1500), [hideControl]); @@ -144,7 +143,7 @@ function BaseVideoPlayer({ return; } setControlStatusState(CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW); - controlsOpacity.value = 1; + controlsOpacity.set(1); }, [controlStatusState, controlsOpacity, hideControl]); const showPopoverMenu = (event?: GestureResponderEvent | KeyboardEvent) => { diff --git a/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx index 5d1ea0d85d0b..69273c1444e8 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx @@ -30,13 +30,12 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { const wasVideoPlayingOnCheck = useSharedValue(false); const onCheckVideoPlaying = (isPlaying: boolean) => { - // eslint-disable-next-line react-compiler/react-compiler - wasVideoPlayingOnCheck.value = isPlaying; + wasVideoPlayingOnCheck.set(isPlaying); }; const progressBarInteraction = (event: GestureUpdateEvent | GestureStateChangeEvent) => { const progress = getProgress(event.x, sliderWidth); - progressWidth.value = progress; + progressWidth.set(progress); runOnJS(seekPosition)((progress * duration) / 100); }; @@ -56,7 +55,7 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { }) .onFinalize(() => { runOnJS(setIsSliderPressed)(false); - if (!wasVideoPlayingOnCheck.value) { + if (!wasVideoPlayingOnCheck.get()) { return; } runOnJS(playVideo)(); @@ -66,10 +65,10 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { if (isSliderPressed) { return; } - progressWidth.value = getProgress(position, duration); + progressWidth.set(getProgress(position, duration)); }, [duration, isSliderPressed, position, progressWidth]); - const progressBarStyle: ViewStyle = useAnimatedStyle(() => ({width: `${progressWidth.value}%`})); + const progressBarStyle: ViewStyle = useAnimatedStyle(() => ({width: `${progressWidth.get()}%`})); return ( diff --git a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx index 751bd1b1df26..a6e44607ea62 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx @@ -35,7 +35,7 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { const {translate} = useLocalize(); const {updateVolume, volume} = useVolumeContext(); const [sliderHeight, setSliderHeight] = useState(1); - const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.value)}); + const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.get())}); const [isSliderBeingUsed, setIsSliderBeingUsed] = useState(false); const onSliderLayout = useCallback((event: LayoutChangeEvent) => { @@ -45,8 +45,7 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { const changeVolumeOnPan = useCallback( (event: GestureStateChangeEvent | GestureUpdateEvent) => { const val = NumberUtils.roundToTwoDecimalPlaces(1 - event.y / sliderHeight); - // eslint-disable-next-line react-compiler/react-compiler - volume.value = NumberUtils.clamp(val, 0, 1); + volume.set(NumberUtils.clamp(val, 0, 1)); }, [sliderHeight, volume], ); @@ -63,15 +62,15 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { runOnJS(setIsSliderBeingUsed)(false); }); - const progressBarStyle = useAnimatedStyle(() => ({height: `${volume.value * 100}%`})); + const progressBarStyle = useAnimatedStyle(() => ({height: `${volume.get() * 100}%`})); const updateIcon = useCallback((vol: number) => { setVolumeIcon({icon: getVolumeIcon(vol)}); }, []); useDerivedValue(() => { - runOnJS(updateVolume)(volume.value); - runOnJS(updateIcon)(volume.value); + runOnJS(updateVolume)(volume.get()); + runOnJS(updateIcon)(volume.get()); }, [volume]); return ( @@ -95,8 +94,8 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { )} updateVolume(volume.value === 0 ? 1 : 0)} + tooltipText={volume.get() === 0 ? translate('videoPlayer.unmute') : translate('videoPlayer.mute')} + onPress={() => updateVolume(volume.get() === 0 ? 1 : 0)} src={volumeIcon.icon} small={small} shouldForceRenderingTooltipBelow diff --git a/src/components/VideoPlayerContexts/VolumeContext.tsx b/src/components/VideoPlayerContexts/VolumeContext.tsx index f22b524848de..1f7b3bf66551 100644 --- a/src/components/VideoPlayerContexts/VolumeContext.tsx +++ b/src/components/VideoPlayerContexts/VolumeContext.tsx @@ -16,8 +16,7 @@ function VolumeContextProvider({children}: ChildrenProps) { return; } currentVideoPlayerRef.current.setStatusAsync({volume: newVolume, isMuted: newVolume === 0}); - // eslint-disable-next-line react-compiler/react-compiler - volume.value = newVolume; + volume.set(newVolume); }, [currentVideoPlayerRef, volume], ); @@ -28,8 +27,8 @@ function VolumeContextProvider({children}: ChildrenProps) { if (!originalParent) { return; } - updateVolume(volume.value); - }, [originalParent, updateVolume, volume.value]); + updateVolume(volume.get()); + }, [originalParent, updateVolume, volume]); const contextValue = useMemo(() => ({updateVolume, volume}), [updateVolume, volume]); return {children}; diff --git a/src/hooks/useAnimatedHighlightStyle/index.ts b/src/hooks/useAnimatedHighlightStyle/index.ts index e17f30fc60bf..4c9099c7f1ba 100644 --- a/src/hooks/useAnimatedHighlightStyle/index.ts +++ b/src/hooks/useAnimatedHighlightStyle/index.ts @@ -66,9 +66,9 @@ export default function useAnimatedHighlightStyle({ const theme = useTheme(); const highlightBackgroundStyle = useAnimatedStyle(() => ({ - backgroundColor: interpolateColor(repeatableProgress.value, [0, 1], [backgroundColor ?? theme.appBG, highlightColor ?? theme.border]), - height: height ? interpolate(nonRepeatableProgress.value, [0, 1], [0, height]) : 'auto', - opacity: interpolate(nonRepeatableProgress.value, [0, 1], [0, 1]), + backgroundColor: interpolateColor(repeatableProgress.get(), [0, 1], [backgroundColor ?? theme.appBG, highlightColor ?? theme.border]), + height: height ? interpolate(nonRepeatableProgress.get(), [0, 1], [0, height]) : 'auto', + opacity: interpolate(nonRepeatableProgress.get(), [0, 1], [0, 1]), borderRadius, })); @@ -90,19 +90,22 @@ export default function useAnimatedHighlightStyle({ setStartHighlight(false); InteractionManager.runAfterInteractions(() => { runOnJS(() => { - nonRepeatableProgress.value = withDelay( - itemEnterDelay, - withTiming(1, {duration: itemEnterDuration, easing: Easing.inOut(Easing.ease)}, (finished) => { - if (!finished) { - return; - } - - // eslint-disable-next-line react-compiler/react-compiler - repeatableProgress.value = withSequence( - withDelay(highlightStartDelay, withTiming(1, {duration: highlightStartDuration, easing: Easing.inOut(Easing.ease)})), - withDelay(highlightEndDelay, withTiming(0, {duration: highlightEndDuration, easing: Easing.inOut(Easing.ease)})), - ); - }), + nonRepeatableProgress.set( + withDelay( + itemEnterDelay, + withTiming(1, {duration: itemEnterDuration, easing: Easing.inOut(Easing.ease)}, (finished) => { + if (!finished) { + return; + } + + repeatableProgress.set( + withSequence( + withDelay(highlightStartDelay, withTiming(1, {duration: highlightStartDuration, easing: Easing.inOut(Easing.ease)})), + withDelay(highlightEndDelay, withTiming(0, {duration: highlightEndDuration, easing: Easing.inOut(Easing.ease)})), + ), + ); + }), + ), ); })(); }); diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index 38efb8eb929f..a34e8a47e0cb 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -37,7 +37,7 @@ function SearchPageBottomTab() { const scrollOffset = useSharedValue(0); const topBarOffset = useSharedValue(variables.searchHeaderHeight); const topBarAnimatedStyle = useAnimatedStyle(() => ({ - top: topBarOffset.value, + top: topBarOffset.get(), })); const scrollHandler = useAnimatedScrollHandler({ @@ -47,15 +47,14 @@ function SearchPageBottomTab() { return; } const currentOffset = contentOffset.y; - const isScrollingDown = currentOffset > scrollOffset.value; - const distanceScrolled = currentOffset - scrollOffset.value; + const isScrollingDown = currentOffset > scrollOffset.get(); + const distanceScrolled = currentOffset - scrollOffset.get(); if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) { - // eslint-disable-next-line react-compiler/react-compiler - topBarOffset.value = clamp(topBarOffset.value - distanceScrolled, variables.minimalTopBarOffset, variables.searchHeaderHeight); + topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, variables.searchHeaderHeight)); } else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) { - topBarOffset.value = withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}); + topBarOffset.set(withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); } - scrollOffset.value = currentOffset; + scrollOffset.set(currentOffset); }, }); @@ -113,7 +112,7 @@ function SearchPageBottomTab() { { - topBarOffset.value = withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}); + topBarOffset.set(withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); }} /> diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx index d277862ebf2c..56ae27de5ea1 100644 --- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx +++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx @@ -35,13 +35,12 @@ function AnimatedEmptyStateBackground() { * We use x and y gyroscope velocity and add it to position offset to move background based on device movements. * Position the phone was in while entering the screen is the initial position for background image. */ - const {x, y} = animatedSensor.sensor.value; + const {x, y} = animatedSensor.sensor.get(); // The x vs y here seems wrong but is the way to make it feel right to the user - // eslint-disable-next-line react-compiler/react-compiler - xOffset.value = clamp(xOffset.value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X); - yOffset.value = clamp(yOffset.value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y); + xOffset.set((value) => clamp(value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X)); + yOffset.set((value) => clamp(value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y)); return { - transform: [{translateX: withSpring(xOffset.value)}, {translateY: withSpring(yOffset.value, {overshootClamping: true})}, {scale: 1.15}], + transform: [{translateX: withSpring(xOffset.get())}, {translateY: withSpring(yOffset.get(), {overshootClamping: true})}, {scale: 1.15}], }; }, [isReducedMotionEnabled]); diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 0d92946e3d66..a196704d2119 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -30,13 +30,13 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const show = useCallback(() => { 'worklet'; - translateY.value = withSpring(MARKER_ACTIVE_TRANSLATE_Y); + translateY.set(withSpring(MARKER_ACTIVE_TRANSLATE_Y)); }, [translateY]); const hide = useCallback(() => { 'worklet'; - translateY.value = withSpring(MARKER_INACTIVE_TRANSLATE_Y); + translateY.set(withSpring(MARKER_INACTIVE_TRANSLATE_Y)); }, [translateY]); useEffect(() => { @@ -49,7 +49,7 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const wrapperStyle = useAnimatedStyle(() => ({ ...styles.floatingMessageCounterWrapper, - transform: [{translateY: translateY.value}], + transform: [{translateY: translateY.get()}], })); return ( diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 1cb70fe6c926..9042c30440f7 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -709,20 +709,19 @@ function ComposerWithSuggestions( useEffect(() => { // We use the tag to store the native ID of the text input. Later, we use it in onSelectionChange to pick up the proper text input data. + tag.set(findNodeHandle(textInputRef.current) ?? -1); + }, [tag]); - tag.value = findNodeHandle(textInputRef.current) ?? -1; - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, []); useFocusedInputHandler( { onSelectionChange: (event) => { 'worklet'; - if (event.target === tag.value) { - cursorPositionValue.value = { + if (event.target === tag.get()) { + cursorPositionValue.set({ x: event.selection.end.x, y: event.selection.end.y, - }; + }); } }, }, @@ -731,7 +730,7 @@ function ComposerWithSuggestions( const measureParentContainerAndReportCursor = useCallback( (callback: MeasureParentContainerAndCursorCallback) => { const {scrollValue} = getScrollPosition({mobileInputScrollPosition, textInputRef}); - const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.value, positionOnWeb: selection}); + const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.get(), positionOnWeb: selection}); measureParentContainer((x, y, width, height) => { callback({ x, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 23b059f2fda2..4673475063c8 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -342,7 +342,7 @@ function ReportActionCompose({ const handleSendMessage = useCallback(() => { 'worklet'; - const clearComposer = composerRefShared.value.clear; + const clearComposer = composerRefShared.get().clear; if (!clearComposer) { throw new Error('The composerRefShared.clear function is not set yet. This should never happen, and indicates a developer error.'); } @@ -468,10 +468,9 @@ function ReportActionCompose({ { composerRef.current = ref ?? undefined; - // eslint-disable-next-line react-compiler/react-compiler - composerRefShared.value = { + composerRefShared.set({ clear: ref?.clear, - }; + }); }} suggestionsRef={suggestionsRef} isNextModalWillOpenRef={isNextModalWillOpenRef} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index fd2dc2d44d4b..8bcf5a462d38 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -417,7 +417,7 @@ function ReportActionItemMessageEdit( const measureParentContainerAndReportCursor = useCallback( (callback: MeasureParentContainerAndCursorCallback) => { const {scrollValue} = getScrollPosition({mobileInputScrollPosition, textInputRef}); - const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.value, positionOnWeb: selection}); + const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.get(), positionOnWeb: selection}); measureContainer((x, y, width, height) => { callback({ x, @@ -429,25 +429,23 @@ function ReportActionItemMessageEdit( }); }); }, - [cursorPositionValue.value, measureContainer, selection], + [cursorPositionValue, measureContainer, selection], ); useEffect(() => { // We use the tag to store the native ID of the text input. Later, we use it in onSelectionChange to pick up the proper text input data. - - // eslint-disable-next-line react-compiler/react-compiler - tag.value = findNodeHandle(textInputRef.current) ?? -1; + tag.set(findNodeHandle(textInputRef.current) ?? -1); }, [tag]); useFocusedInputHandler( { onSelectionChange: (event) => { 'worklet'; - if (event.target === tag.value) { - cursorPositionValue.value = { + if (event.target === tag.get()) { + cursorPositionValue.set({ x: event.selection.end.x, y: event.selection.end.y, - }; + }); } }, }, diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index d3f0c9cb496d..61c856c856cf 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -120,8 +120,8 @@ function IOURequestStepScan({ const focusIndicatorPosition = useSharedValue({x: 0, y: 0}); const cameraFocusIndicatorAnimatedStyle = useAnimatedStyle(() => ({ - opacity: focusIndicatorOpacity.value, - transform: [{translateX: focusIndicatorPosition.value.x}, {translateY: focusIndicatorPosition.value.y}, {scale: focusIndicatorScale.value}], + opacity: focusIndicatorOpacity.get(), + transform: [{translateX: focusIndicatorPosition.get().x}, {translateY: focusIndicatorPosition.get().y}, {scale: focusIndicatorScale.get()}], })); const focusCamera = (point: Point) => { @@ -143,11 +143,10 @@ function IOURequestStepScan({ .onStart((ev: {x: number; y: number}) => { const point = {x: ev.x, y: ev.y}; - // eslint-disable-next-line react-compiler/react-compiler - focusIndicatorOpacity.value = withSequence(withTiming(0.8, {duration: 250}), withDelay(1000, withTiming(0, {duration: 250}))); - focusIndicatorScale.value = 2; - focusIndicatorScale.value = withSpring(1, {damping: 10, stiffness: 200}); - focusIndicatorPosition.value = point; + focusIndicatorOpacity.set(withSequence(withTiming(0.8, {duration: 250}), withDelay(1000, withTiming(0, {duration: 250})))); + focusIndicatorScale.set(2); + focusIndicatorScale.set(withSpring(1, {damping: 10, stiffness: 200})); + focusIndicatorPosition.set(point); runOnJS(focusCamera)(point); }); diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx b/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx index 75e0be7e5f7c..fc28dc08fb15 100644 --- a/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx @@ -20,14 +20,15 @@ function BackgroundImage({width, transitionDuration, isSmallScreen = false}: Bac const isAnonymous = isAnonymousUser(); const opacity = useSharedValue(0); - const animatedStyle = useAnimatedStyle(() => ({opacity: opacity.value})); + const animatedStyle = useAnimatedStyle(() => ({opacity: opacity.get()})); // This sets the opacity animation for the background image once it has loaded. function setOpacityAnimation() { - // eslint-disable-next-line react-compiler/react-compiler - opacity.value = withTiming(1, { - duration: CONST.MICROSECONDS_PER_MS, - easing: Easing.ease, - }); + opacity.set( + withTiming(1, { + duration: CONST.MICROSECONDS_PER_MS, + easing: Easing.ease, + }), + ); } useEffect(() => { From 70253160c8bc659506327794b52ee853bfab2a08 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Nov 2024 15:03:51 +0100 Subject: [PATCH 018/225] Refactor LoadingBar to use setter methods for animated values --- src/components/LoadingBar.tsx | 75 +++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/components/LoadingBar.tsx b/src/components/LoadingBar.tsx index 163ffe2aa66b..5650c3fe38ac 100644 --- a/src/components/LoadingBar.tsx +++ b/src/components/LoadingBar.tsx @@ -17,44 +17,49 @@ function LoadingBar({shouldShow}: LoadingBarProps) { useEffect(() => { if (shouldShow) { - // eslint-disable-next-line react-compiler/react-compiler - isVisible.value = true; - left.value = 0; - width.value = 0; - opacity.value = withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}); - left.value = withDelay( - CONST.ANIMATED_PROGRESS_BAR_DELAY, - withRepeat( - withSequence( - withTiming(0, {duration: 0}), - withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), - withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + isVisible.set(true); + left.set(0); + width.set(0); + opacity.set(withTiming(1, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION})); + left.set( + withDelay( + CONST.ANIMATED_PROGRESS_BAR_DELAY, + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, ), - -1, - false, ), ); - width.value = withDelay( - CONST.ANIMATED_PROGRESS_BAR_DELAY, - withRepeat( - withSequence( - withTiming(0, {duration: 0}), - withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), - withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + width.set( + withDelay( + CONST.ANIMATED_PROGRESS_BAR_DELAY, + withRepeat( + withSequence( + withTiming(0, {duration: 0}), + withTiming(100, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_DURATION, easing: Easing.bezier(0.65, 0, 0.35, 1)}), + ), + -1, + false, ), - -1, - false, ), ); - } else if (isVisible.value) { - opacity.value = withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { - runOnJS(() => { - isVisible.value = false; - cancelAnimation(left); - cancelAnimation(width); - }); - }); + } else if (isVisible.get()) { + opacity.set( + withTiming(0, {duration: CONST.ANIMATED_PROGRESS_BAR_OPACITY_DURATION}, () => { + runOnJS(() => { + isVisible.set(false); + cancelAnimation(left); + cancelAnimation(width); + }); + }), + ); } // we want to update only when shouldShow changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps @@ -62,20 +67,20 @@ function LoadingBar({shouldShow}: LoadingBarProps) { const animatedIndicatorStyle = useAnimatedStyle(() => { return { - left: `${left.value}%`, - width: `${width.value}%`, + left: `${left.get()}%`, + width: `${width.get()}%`, }; }); const animatedContainerStyle = useAnimatedStyle(() => { return { - opacity: opacity.value, + opacity: opacity.get(), }; }); return ( - {isVisible.value ? : null} + {isVisible.get() ? : null} ); } From 6a6e6b1d808f7bbfba7b85a5c1fb23d2b826d123 Mon Sep 17 00:00:00 2001 From: jayeshmangwani Date: Wed, 6 Nov 2024 19:53:51 +0530 Subject: [PATCH 019/225] added new route for profile plan type --- src/ROUTES.ts | 4 ++ src/SCREENS.ts | 1 + src/languages/en.ts | 17 +++++++ src/languages/es.ts | 20 ++++++++ src/languages/params.ts | 6 +++ .../ModalStackNavigators/index.tsx | 1 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 ++ src/libs/PolicyUtils.ts | 12 +++++ src/pages/workspace/WorkspaceProfilePage.tsx | 17 +++++++ .../WorkspaceProfilePlanTypePage.tsx | 48 +++++++++++++++++++ src/pages/workspace/WorkspacesListRow.tsx | 28 ++++------- 12 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 src/pages/workspace/WorkspaceProfilePlanTypePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 45501bf46374..62ced12dd74b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -684,6 +684,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/profile/address', getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/profile/address` as const, backTo), }, + WORKSPACE_PROFILE_PLAN: { + route: 'settings/workspaces/:policyID/profile/plan', + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/profile/plan` as const, backTo), + }, WORKSPACE_ACCOUNTING: { route: 'settings/workspaces/:policyID/accounting', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index dea0f028e1a0..161c8ebe73eb 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -494,6 +494,7 @@ const SCREENS = { TAG_GL_CODE: 'Tag_GL_Code', CURRENCY: 'Workspace_Profile_Currency', ADDRESS: 'Workspace_Profile_Address', + PLAN: 'Workspace_Profile_Plan_Type', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_PAYER: 'Workspace_Workflows_Payer', WORKFLOWS_APPROVALS_NEW: 'Workspace_Approvals_New', diff --git a/src/languages/en.ts b/src/languages/en.ts index 38b11e9fea38..d368cdb85012 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -186,6 +186,7 @@ import type { WelcomeNoteParams, WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, + WorkspaceLockedPlanTypeParams, WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams, YourPlanPriceParams, ZipCodeExampleFormatParams, @@ -2400,6 +2401,7 @@ const translations = { return 'Member'; } }, + planType: 'Plan Type', }, qbd: { exportOutOfPocketExpensesDescription: 'Set how out-of-pocket expenses export to QuickBooks Desktop.', @@ -4187,6 +4189,21 @@ const translations = { andEnableWorkflows: 'and enable workflows, then add approvals to unlock this feature.', }, }, + planTypePage: { + title: 'Plan type', + planTypes: { + team: { + label: 'Collect', + description: 'For teams looking to automate their processes.', + }, + corporate: { + label: 'Control', + description: 'For organizations with advanced requirements.', + }, + }, + lockedPlanNote: ({subscriptionUsersCount, annualSubscriptionEndDate}: WorkspaceLockedPlanTypeParams) => + `You've committed to ${subscriptionUsersCount} active users on the Control plan until your annual subscription ends on ${annualSubscriptionEndDate}. You can switch to pay-per-use subscription and downgrade to the Collect plan starting ${annualSubscriptionEndDate} by disabling auto-renew in`, + }, }, getAssistancePage: { title: 'Get assistance', diff --git a/src/languages/es.ts b/src/languages/es.ts index 11a31c836add..f20a5a434eba 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -185,6 +185,7 @@ import type { WelcomeNoteParams, WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, + WorkspaceLockedPlanTypeParams, WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams, YourPlanPriceParams, ZipCodeExampleFormatParams, @@ -2423,6 +2424,7 @@ const translations = { return 'Miembro'; } }, + planType: 'Tipo de plan', }, qbd: { exportOutOfPocketExpensesDescription: 'Establezca cómo se exportan los gastos de bolsillo a QuickBooks Desktop.', @@ -4062,6 +4064,21 @@ const translations = { confirmText: 'Sí, exportar de nuevo', cancelText: 'Cancelar', }, + planTypePage: { + title: 'Tipo de plan', + planTypes: { + team: { + label: 'Collect', + description: 'Para equipos que buscan automatizar sus procesos.', + }, + corporate: { + label: 'Recolectar', + description: 'Para organizaciones con requisitos avanzados.', + }, + }, + lockedPlanNote: ({subscriptionUsersCount, annualSubscriptionEndDate}: WorkspaceLockedPlanTypeParams) => + `Te has comprometido a ${subscriptionUsersCount} usuarios activos en el plan Control hasta que termine tu suscripción anual el ${annualSubscriptionEndDate}. Puedes cambiar a una suscripción de pago por uso y desmejorar al plan Collect a partir del ${annualSubscriptionEndDate} deshabilitando la renovación automática en`, + }, upgrade: { reportFields: { title: 'Los campos', @@ -4233,6 +4250,9 @@ const translations = { andEnableWorkflows: 'y habilita los flujos de trabajo, luego añade aprobaciones para desbloquear esta función.', }, }, + planType: { + title: 'Tipo de plan', + }, }, getAssistancePage: { title: 'Obtener ayuda', diff --git a/src/languages/params.ts b/src/languages/params.ts index e9f0c4370357..c1c5fb5f32d9 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -547,6 +547,11 @@ type CompanyCardBankName = { bankName: string; }; +type WorkspaceLockedPlanTypeParams = { + subscriptionUsersCount: number; + annualSubscriptionEndDate: string; +}; + export type { AuthenticationErrorParams, ImportMembersSuccessfullDescriptionParams, @@ -746,4 +751,5 @@ export type { OptionalParam, AssignCardParams, ImportedTypesParams, + WorkspaceLockedPlanTypeParams, }; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 8a64424c8f7d..8cf6168faf96 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -269,6 +269,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/WorkspaceProfileCurrencyPage').default, [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../../pages/workspace/categories/CategorySettingsPage').default, [SCREENS.WORKSPACE.ADDRESS]: () => require('../../../../pages/workspace/WorkspaceProfileAddressPage').default, + [SCREENS.WORKSPACE.PLAN]: () => require('../../../../pages/workspace/WorkspaceProfilePlanTypePage').default, [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default, [SCREENS.WORKSPACE.CATEGORIES_IMPORT]: () => require('../../../../pages/workspace/categories/ImportCategoriesPage').default, [SCREENS.WORKSPACE.CATEGORIES_IMPORTED]: () => require('../../../../pages/workspace/categories/ImportedCategoriesPage').default, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index d282bab770c6..ff887dd43259 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -5,6 +5,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { [SCREENS.WORKSPACE.PROFILE]: [ SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.ADDRESS, + SCREENS.WORKSPACE.PLAN, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 330d5f113503..ac68a0cdf59a 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -347,6 +347,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ADDRESS]: { path: ROUTES.WORKSPACE_PROFILE_ADDRESS.route, }, + [SCREENS.WORKSPACE.PLAN]: { + path: ROUTES.WORKSPACE_PROFILE_PLAN.route, + }, [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.route}, [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.route}, [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CLASSES]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES.route}, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index c596357585bc..29f6a9675d5f 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1064,6 +1064,17 @@ function getActivePolicy(): OnyxEntry { return getPolicy(activePolicyId); } +function getUserFriendlyWorkspaceType(workspaceType: ValueOf) { + switch (workspaceType) { + case CONST.POLICY.TYPE.CORPORATE: + return Localize.translateLocal('workspace.type.control'); + case CONST.POLICY.TYPE.TEAM: + return Localize.translateLocal('workspace.type.collect'); + default: + return Localize.translateLocal('workspace.type.free'); + } +} + export { canEditTaxRate, extractPolicyIDFromPath, @@ -1181,6 +1192,7 @@ export { getNetSuiteImportCustomFieldLabel, getAllPoliciesLength, getActivePolicy, + getUserFriendlyWorkspaceType, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 979df0099d82..dc5f1ddedee9 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -74,6 +74,7 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, route}: Workspac const onPressName = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_NAME.getRoute(policy?.id ?? '-1')), [policy?.id]); const onPressDescription = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(policy?.id ?? '-1')), [policy?.id]); const onPressShare = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_SHARE.getRoute(policy?.id ?? '-1')), [policy?.id]); + const onPressPlanType = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_PLAN.getRoute(policy?.id ?? '-1')), [policy?.id]); const policyName = policy?.name ?? ''; const policyDescription = @@ -264,6 +265,22 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, route}: Workspac )} + {!readOnly && !!policy?.type && ( + + + + + + )} {!readOnly && (