From ece06a0bc920c36f11f7b64aba77000f00c9947a Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 20 Nov 2023 15:03:16 +0700 Subject: [PATCH 0001/1233] fix: login via deeplink should open modal in request money --- src/pages/EditRequestPage.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 194cd2855dbd..e2f2d44127ca 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -3,8 +3,10 @@ import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import categoryPropTypes from '@components/categoryPropTypes'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; import tagPropTypes from '@components/tagPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -61,6 +63,9 @@ const propTypes = { /** Transaction that stores the request data */ transaction: transactionPropTypes, + + /** Indicates whether the app is loading initial data */ + isLoadingReportData: PropTypes.bool, }; const defaultProps = { @@ -70,11 +75,18 @@ const defaultProps = { policyTags: {}, parentReportActions: {}, transaction: {}, + isLoadingReportData: true, }; -function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction, isLoadingReportData}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); + + const isTransactionLoadingRoute = lodashGet(transaction, 'comment.isLoading', false); + const isTransactionLoading = lodashGet(transaction, 'isLoading', false); + + const isDataLoading = isLoadingReportData || isTransactionLoading || isTransactionLoadingRoute || _.isEmpty(transaction); + const { amount: transactionAmount, currency: transactionCurrency, @@ -107,7 +119,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit)) { + if (isDataLoading || ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit)) { return; } @@ -115,7 +127,11 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [parentReportAction, parentReport.reportID, fieldToEdit]); + }, [parentReportAction, parentReport.reportID, fieldToEdit, isDataLoading]); + + if (isDataLoading) { + return ; + } // Update the transaction object and close the modal function editMoneyRequest(transactionChanges) { @@ -276,6 +292,9 @@ export default compose( report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, }, + isLoadingReportData: { + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + }, }), // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ From 585b3f997921f3ee9abf7380e037079dbc111465 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 20 Nov 2023 16:57:00 +0700 Subject: [PATCH 0002/1233] fix: conditions --- src/pages/EditRequestPage.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index e2f2d44127ca..56dabdc137b1 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -10,14 +10,17 @@ import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; import tagPropTypes from '@components/tagPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; +import withWindowDimensions from '@components/withWindowDimensions'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; +import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -66,6 +69,9 @@ const propTypes = { /** Indicates whether the app is loading initial data */ isLoadingReportData: PropTypes.bool, + + /** Is the window width narrow, like on a mobile device */ + isSmallScreenWidth: PropTypes.bool.isRequired, }; const defaultProps = { @@ -78,9 +84,10 @@ const defaultProps = { isLoadingReportData: true, }; -function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction, isLoadingReportData}) { +function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction, isLoadingReportData, isSmallScreenWidth}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); + const parentReportID = lodashGet(report, 'parentReportID', '0'); const isTransactionLoadingRoute = lodashGet(transaction, 'comment.isLoading', false); const isTransactionLoading = lodashGet(transaction, 'isLoading', false); @@ -116,6 +123,18 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT // A flag for showing the tags page const shouldShowTags = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagList))); + const reportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID); + + // For small screen, we don't call openReport API when we go to a sub report page by deeplink + // So we need to call openReport here for small screen + useEffect(() => { + if (!isSmallScreenWidth || (!_.isEmpty(report) && !_.isEmpty(reportAction))) { + return; + } + Report.openReport(route.params.threadReportID); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSmallScreenWidth, route.params.threadReportID]); + // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. @@ -288,6 +307,7 @@ EditRequestPage.displayName = 'EditRequestPage'; EditRequestPage.propTypes = propTypes; EditRequestPage.defaultProps = defaultProps; export default compose( + withWindowDimensions, withOnyx({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, From 7adeaefd99d45e64f56bb1ffdb2bb594acbcaee5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 20 Nov 2023 18:36:20 +0700 Subject: [PATCH 0003/1233] open report when transaction is empty --- src/pages/EditRequestPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 56dabdc137b1..dbfebc300eac 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -128,7 +128,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT // For small screen, we don't call openReport API when we go to a sub report page by deeplink // So we need to call openReport here for small screen useEffect(() => { - if (!isSmallScreenWidth || (!_.isEmpty(report) && !_.isEmpty(reportAction))) { + if (!isSmallScreenWidth || (!_.isEmpty(report) && !_.isEmpty(reportAction) && !_.isEmpty(transaction))) { return; } Report.openReport(route.params.threadReportID); From 002c90986ddad81fa5d59fa8b4963c40b51c225c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 3 Jan 2024 14:46:11 +0700 Subject: [PATCH 0004/1233] fix lint --- src/pages/EditRequestPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index dbbdbd1d1db1..c4903fc3748c 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -143,7 +143,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [parentReportAction, parentReport.reportID, fieldToEdit, isDataLoading]); + }, [parentReportAction, parentReport.reportID, fieldToEdit, transaction, isDataLoading]); if (isDataLoading) { return ; From 58e7687e22d32a22de13fb8a7f35cf5c1dc9137c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 3 Jan 2024 15:41:28 +0700 Subject: [PATCH 0005/1233] fix lint --- src/pages/EditRequestPage.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index c4903fc3748c..344a0c8a7176 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -145,10 +145,6 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT }); }, [parentReportAction, parentReport.reportID, fieldToEdit, transaction, isDataLoading]); - if (isDataLoading) { - return ; - } - // Update the transaction object and close the modal function editMoneyRequest(transactionChanges) { IOU.editMoneyRequest(transaction, report.reportID, transactionChanges); @@ -184,6 +180,10 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT [transaction, report], ); + if (isDataLoading) { + return ; + } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { return ( Date: Thu, 11 Jan 2024 16:23:45 +0700 Subject: [PATCH 0006/1233] fix lint --- src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 53a3b1234e2c..d6c925cd749a 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -2,12 +2,12 @@ import {useEffect} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import usePermissions from '@hooks/usePermissions'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, Report, ReportMetadata} from '@src/types/onyx'; import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; -import Navigation from '../Navigation'; type ReportScreenIDSetterComponentProps = { /** Available reports that would be displayed in this navigator */ From 087cbcddeb4ace851c591b571bb62da0b5492ee0 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 20 Mar 2024 15:09:31 +0300 Subject: [PATCH 0007/1233] implemented confirmed route for report preview --- .../ReportActionItemImages.tsx | 27 +++++++++++++------ .../ReportActionItem/ReportPreview.tsx | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index ffc12957dcb4..dc9ed0d371fb 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -2,11 +2,13 @@ import React from 'react'; import {View} from 'react-native'; import {Polygon, Svg} from 'react-native-svg'; +import ConfirmedRoute from '@components/ConfirmedRoute'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type {ThumbnailAndImageURI} from '@libs/ReceiptUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import ReportActionItemImage from './ReportActionItemImage'; @@ -70,19 +72,28 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report // Show a border to separate multiple images. Shown to the right for each except the last. const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; + const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); + const hasPendingWaypoints = transaction?.pendingFields?.waypoints; + const showMapAsImage = isDistanceRequest && hasPendingWaypoints; return ( - + {showMapAsImage ? ( + + + + ) : ( + + )} {isLastImage && remaining > 0 && ( diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f1aa1751dd84..57fa568eb487 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -135,7 +135,7 @@ function ReportPreview({ const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; const hasErrors = hasMissingSmartscanFields || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations)); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); - const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); + const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); let formattedMerchant = numberOfRequests === 1 && hasReceipts ? TransactionUtils.getMerchant(transactionsWithReceipts[0]) : null; if (TransactionUtils.isPartialMerchant(formattedMerchant ?? '')) { From eb4b183fcfbcce82e4277c84dc1aeb1a62a2e480 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 11:29:12 +0700 Subject: [PATCH 0008/1233] feature: Add validation flow to bank account set up --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/actions/User.ts | 7 +++++++ src/pages/ReimbursementAccount/BankAccountStep.js | 15 +++++++++++++++ 4 files changed, 24 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 29618b083bd5..60c051a289b1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1413,6 +1413,7 @@ export default { validateAccountError: { phrase1: 'Hold up! We need you to validate your account first. To do so, ', phrase2: 'sign back in with a magic code', + phrase3: 'o verifique la cuenta aquí', }, hasPhoneLoginError: 'To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.', hasBeenThrottledError: 'There was an error adding your bank account. Please wait a few minutes and try again.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 874921ee911a..811bfd2031d4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1432,6 +1432,7 @@ export default { validateAccountError: { phrase1: '¡Un momento! Primero necesitas validar tu cuenta. Para hacerlo, ', phrase2: 'vuelve a iniciar sesión con un código mágico', + phrase3: '', }, hasPhoneLoginError: 'Para añadir una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes añadir tu número de teléfono como nombre de usuario secundario.', diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 2d23edfba93f..e2bc4791d465 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -390,6 +390,13 @@ function validateSecondaryLogin(contactMethod: string, validateCode: string) { isLoading: true, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.USER, + value: { + validated: true, + }, + }, ]; const successData: OnyxUpdate[] = [ { diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index e8aacadc6e1a..0ebcb1fe891e 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -20,6 +20,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import getPlaidDesktopMessage from '@libs/getPlaidDesktopMessage'; +import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import * as BankAccounts from '@userActions/BankAccounts'; import * as Link from '@userActions/Link'; @@ -83,6 +84,7 @@ function BankAccountStep(props) { props.policyID, ROUTES.WORKSPACE_INITIAL.getRoute(props.policyID), )}`; + const loginNames = _.keys(props.loginList); const removeExistingBankAccountDetails = () => { const bankAccountData = { @@ -180,6 +182,16 @@ function BankAccountStep(props) { > {props.translate('bankAccount.validateAccountError.phrase2')} + { + const login = props.loginList[loginNames[0]]; + + Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(login.partnerUserID || loginNames[0])); + }} + > + {props.translate('bankAccount.validateAccountError.phrase3')} + . @@ -221,5 +233,8 @@ export default compose( isPlaidDisabled: { key: ONYXKEYS.IS_PLAID_DISABLED, }, + loginList: { + key: ONYXKEYS.LOGIN_LIST, + }, }), )(BankAccountStep); From cd70586476cf318c6fc0b6da01d0ec7a12a28356 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 28 Mar 2024 22:50:35 +0300 Subject: [PATCH 0009/1233] minor fixes --- src/components/ReportActionItem/ReportActionItemImages.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 53560307b314..921bb2271c69 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -71,8 +71,8 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report // Show a border to separate multiple images. Shown to the right for each except the last. const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; - const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const hasPendingWaypoints = transaction?.pendingFields?.waypoints; + const isDistanceRequest = transaction && TransactionUtils.isDistanceRequest(transaction); + const hasPendingWaypoints = transaction && TransactionUtils.isFetchingWaypointsFromServer(transaction); const showMapAsImage = isDistanceRequest && hasPendingWaypoints; return ( {showMapAsImage ? ( - + ) : ( From 2c9bc81253da3936fdb343b76f02283c462578d7 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 8 Apr 2024 15:36:35 +0700 Subject: [PATCH 0010/1233] add support for latin characters in emoji suggestion --- src/CONST.ts | 4 ++-- src/libs/EmojiTrie.ts | 6 +++++- src/libs/GetStyledTextArray.ts | 5 ++++- src/libs/StringUtils.ts | 11 ++++++++++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index f9229d5185b4..d70d4c5d72d9 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1680,8 +1680,8 @@ const CONST = { // Extract attachment's source from the data's html string ATTACHMENT_DATA: /(data-expensify-source|data-name)="([^"]+)"/g, - EMOJI_NAME: /:[\w+-]+:/g, - EMOJI_SUGGESTIONS: /:[a-zA-Z0-9_+-]{1,40}$/, + EMOJI_NAME: /:[\p{L}0-9_+-]+:/gu, + EMOJI_SUGGESTIONS: /:[\p{L}0-9_+-]{1,40}$/u, AFTER_FIRST_LINE_BREAK: /\n.*/g, LINE_BREAK: /\r|\n/g, CODE_2FA: /^\d{6}$/, diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index d0f0b0dcfab6..4b4994125ef2 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -2,6 +2,7 @@ import emojis, {localeEmojis} from '@assets/emojis'; import type {Emoji, HeaderEmoji, PickerEmoji} from '@assets/emojis/types'; import CONST from '@src/CONST'; import Timing from './actions/Timing'; +import StringUtils from './StringUtils'; import Trie from './Trie'; type EmojiMetaData = { @@ -33,7 +34,10 @@ function addKeywordsToTrie(trie: Trie, keywords: string[], item: keywords.forEach((keyword) => { const keywordNode = trie.search(keyword); if (!keywordNode) { - trie.add(keyword, {suggestions: [{code: item.code, types: item.types, name}]}); + const keywordWithoutAccents = StringUtils.normalizeAccents(keyword); + if (keywordWithoutAccents !== keyword) { + trie.add(keywordWithoutAccents, {suggestions: [{code: item.code, types: item.types, name}]}); + } } else { const suggestion = {code: item.code, types: item.types, name}; const suggestions = shouldPrependKeyword ? [suggestion, ...(keywordNode.metaData.suggestions ?? [])] : [...(keywordNode.metaData.suggestions ?? []), suggestion]; diff --git a/src/libs/GetStyledTextArray.ts b/src/libs/GetStyledTextArray.ts index 9bc6ccc33b4c..ffae31dc861b 100644 --- a/src/libs/GetStyledTextArray.ts +++ b/src/libs/GetStyledTextArray.ts @@ -1,4 +1,5 @@ import Str from 'expensify-common/lib/str'; +import StringUtils from './StringUtils'; type StyledText = { text: string; @@ -8,7 +9,9 @@ type StyledText = { const getStyledTextArray = (name: string, prefix: string): StyledText[] => { const texts = []; const prefixLowercase = prefix.toLowerCase(); - const prefixLocation = name.toLowerCase().search(Str.escapeForRegExp(prefixLowercase)); + const prefixLocation = StringUtils.normalizeAccents(name) + .toLowerCase() + .search(Str.escapeForRegExp(StringUtils.normalizeAccents(prefixLowercase))); if (prefixLocation === 0 && prefix.length === name.length) { texts.push({text: name, isColored: true}); diff --git a/src/libs/StringUtils.ts b/src/libs/StringUtils.ts index 2fb918c7a233..c9fa40c4da8a 100644 --- a/src/libs/StringUtils.ts +++ b/src/libs/StringUtils.ts @@ -63,6 +63,15 @@ function removeInvisibleCharacters(value: string): string { return result.trim(); } +/** + * Remove accents/diacritics + * @param text - The input string + * @returns The string with all accents/diacritics removed + */ +function normalizeAccents(text: string) { + return text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); +} + /** * Replace all CRLF with LF * @param value - The input string @@ -72,4 +81,4 @@ function normalizeCRLF(value?: string): string | undefined { return value?.replace(/\r\n/g, '\n'); } -export default {sanitizeString, isEmptyString, removeInvisibleCharacters, normalizeCRLF}; +export default {sanitizeString, isEmptyString, removeInvisibleCharacters, normalizeAccents, normalizeCRLF}; From 723d36568be6b1c1d8604caa75f9e577e3fb4c27 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 8 Apr 2024 17:33:26 +0700 Subject: [PATCH 0011/1233] add original keyword to trie --- src/libs/EmojiTrie.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index 4b4994125ef2..d51881e0e5d5 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -35,9 +35,8 @@ function addKeywordsToTrie(trie: Trie, keywords: string[], item: const keywordNode = trie.search(keyword); if (!keywordNode) { const keywordWithoutAccents = StringUtils.normalizeAccents(keyword); - if (keywordWithoutAccents !== keyword) { - trie.add(keywordWithoutAccents, {suggestions: [{code: item.code, types: item.types, name}]}); - } + const keywordToAdd = keywordWithoutAccents !== keyword ? keywordWithoutAccents : keyword; + trie.add(keywordToAdd, {suggestions: [{code: item.code, types: item.types, name}]}); } else { const suggestion = {code: item.code, types: item.types, name}; const suggestions = shouldPrependKeyword ? [suggestion, ...(keywordNode.metaData.suggestions ?? [])] : [...(keywordNode.metaData.suggestions ?? []), suggestion]; From af77cbf86ff775b5db0992b43df8a6ee5e161199 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 8 Apr 2024 17:34:12 +0700 Subject: [PATCH 0012/1233] add original keyword to trie --- src/libs/EmojiTrie.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index d51881e0e5d5..c0fd4434c48f 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -35,8 +35,10 @@ function addKeywordsToTrie(trie: Trie, keywords: string[], item: const keywordNode = trie.search(keyword); if (!keywordNode) { const keywordWithoutAccents = StringUtils.normalizeAccents(keyword); - const keywordToAdd = keywordWithoutAccents !== keyword ? keywordWithoutAccents : keyword; - trie.add(keywordToAdd, {suggestions: [{code: item.code, types: item.types, name}]}); + if (keywordWithoutAccents !== keyword) { + trie.add(keywordWithoutAccents, {suggestions: [{code: item.code, types: item.types, name}]}); + } + trie.add(keyword, {suggestions: [{code: item.code, types: item.types, name}]}); } else { const suggestion = {code: item.code, types: item.types, name}; const suggestions = shouldPrependKeyword ? [suggestion, ...(keywordNode.metaData.suggestions ?? [])] : [...(keywordNode.metaData.suggestions ?? []), suggestion]; From 4828b6270f4cc3a13661b272111fc525730c6c37 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 21:01:55 +0300 Subject: [PATCH 0013/1233] Changed empty state route pending icon --- assets/images/emptystate__routepending.svg | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/assets/images/emptystate__routepending.svg b/assets/images/emptystate__routepending.svg index 90f3296d37d6..aba08554d02f 100644 --- a/assets/images/emptystate__routepending.svg +++ b/assets/images/emptystate__routepending.svg @@ -1 +1,18 @@ - \ No newline at end of file + + + + + + + + + From 77e3ca4cf5c1bdfbf8b60ac3fde93228a374ddf7 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 21:14:46 +0300 Subject: [PATCH 0014/1233] add icon fill color --- src/components/DistanceMapView/index.android.tsx | 3 +++ src/components/MapView/PendingMapView.tsx | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/components/DistanceMapView/index.android.tsx b/src/components/DistanceMapView/index.android.tsx index 168a480c6100..629b05d7bccf 100644 --- a/src/components/DistanceMapView/index.android.tsx +++ b/src/components/DistanceMapView/index.android.tsx @@ -5,6 +5,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MapView from '@components/MapView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type DistanceMapViewProps from './types'; @@ -13,6 +14,7 @@ function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { const [isMapReady, setIsMapReady] = useState(false); const {isOffline} = useNetwork(); const {translate} = useLocalize(); + const theme = useTheme(); return ( <> @@ -33,6 +35,7 @@ function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { title={translate('distance.mapPending.title')} subtitle={isOffline ? translate('distance.mapPending.subtitle') : translate('distance.mapPending.onlineSubtitle')} shouldShowLink={false} + iconColor={theme.border} /> )} diff --git a/src/components/MapView/PendingMapView.tsx b/src/components/MapView/PendingMapView.tsx index 32bf42a14b10..e729d03ad477 100644 --- a/src/components/MapView/PendingMapView.tsx +++ b/src/components/MapView/PendingMapView.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import type {PendingMapViewProps} from './MapViewTypes'; @@ -11,11 +12,13 @@ import type {PendingMapViewProps} from './MapViewTypes'; function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) { const hasTextContent = !_.isEmpty(title) || !_.isEmpty(subtitle); const styles = useThemeStyles(); + const theme = useTheme(); return ( {hasTextContent ? ( )} From 420850de85d9ee701402a027b0692f42814432c7 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 21:43:23 +0300 Subject: [PATCH 0015/1233] down sized icon for multiple previews --- src/components/ConfirmedRoute.tsx | 7 +++++-- src/components/MapView/MapViewTypes.ts | 3 +++ src/components/MapView/PendingMapView.tsx | 7 ++++--- src/components/ReportActionItem/ReportActionItemImages.tsx | 5 ++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index 17c5097b8154..596fc74dbbf6 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -26,9 +26,12 @@ type ConfirmedRoutePropsOnyxProps = { type ConfirmedRouteProps = ConfirmedRoutePropsOnyxProps & { /** Transaction that stores the distance request data */ transaction: OnyxEntry; + + /** Whether the size of the route pending icon is small. */ + isSmallIcon?: boolean; }; -function ConfirmedRoute({mapboxAccessToken, transaction}: ConfirmedRouteProps) { +function ConfirmedRoute({mapboxAccessToken, transaction, isSmallIcon}: ConfirmedRouteProps) { const {isOffline} = useNetwork(); const {route0: route} = transaction?.routes ?? {}; const waypoints = transaction?.comment?.waypoints ?? {}; @@ -102,7 +105,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction}: ConfirmedRouteProps) { styleURL={CONST.MAPBOX.STYLE_URL} /> ) : ( - + ); } diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts index 3fa52c54339b..d5c903fdeccb 100644 --- a/src/components/MapView/MapViewTypes.ts +++ b/src/components/MapView/MapViewTypes.ts @@ -36,6 +36,9 @@ type PendingMapViewProps = { /** Style applied to PendingMapView */ style?: StyleProp; + + /** Whether the size of the route pending icon is small. */ + isSmallIcon?: boolean; }; // Initial state of the map diff --git a/src/components/MapView/PendingMapView.tsx b/src/components/MapView/PendingMapView.tsx index e729d03ad477..2c79a0446f5e 100644 --- a/src/components/MapView/PendingMapView.tsx +++ b/src/components/MapView/PendingMapView.tsx @@ -9,10 +9,11 @@ import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import type {PendingMapViewProps} from './MapViewTypes'; -function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) { +function PendingMapView({title = '', subtitle = '', style, isSmallIcon = false}: PendingMapViewProps) { const hasTextContent = !_.isEmpty(title) || !_.isEmpty(subtitle); const styles = useThemeStyles(); const theme = useTheme(); + const iconSize = isSmallIcon ? variables.iconSizeSuperLarge : variables.iconSizeUltraLarge; return ( {hasTextContent ? ( @@ -27,8 +28,8 @@ function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 921bb2271c69..04f5e5e7fdbb 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -81,7 +81,10 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report > {showMapAsImage ? ( - + ) : ( Date: Tue, 9 Apr 2024 11:12:59 +0300 Subject: [PATCH 0016/1233] change ultra large icon size --- src/styles/variables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 309c90fc631e..e70d2bf5c575 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -81,7 +81,7 @@ export default { iconSizeXLarge: 28, iconSizeExtraLarge: 40, iconSizeSuperLarge: 60, - iconSizeUltraLarge: 120, + iconSizeUltraLarge: 80, iconBottomBar: 24, sidebarAvatarSize: 28, iconHeader: 48, From 3a7f04cdbaa1232244d5c1efe4f1c587c81f7d64 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 9 Apr 2024 13:02:58 +0300 Subject: [PATCH 0017/1233] changed background color --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index f165974119ff..d775fa04920a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4341,7 +4341,7 @@ const styles = (theme: ThemeColors) => }, mapPendingView: { - backgroundColor: theme.highlightBG, + backgroundColor: theme.hoverComponentBG, ...flex.flex1, borderRadius: variables.componentBorderRadiusLarge, }, From 69263a4349afadea85763a340f0d16af1825161c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Apr 2024 04:07:01 +0700 Subject: [PATCH 0018/1233] fix Reddot pinned chat appears for approver for submitter's failed scanned --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/OptionsListUtils.ts | 4 ++-- src/libs/ReportUtils.ts | 20 ++++++++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index dd34d0ca2540..42c85a8ed9dd 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -210,7 +210,7 @@ function MoneyRequestView({ const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; - const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); + const hasErrors = TransactionUtils.hasMissingSmartscanFields(transaction); if (hasReceipt) { receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction); } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index e1a3e9207ad8..7e0de5611c8c 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -509,10 +509,10 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } } else if ((ReportUtils.isIOUReport(report) || ReportUtils.isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { - if (ReportUtils.hasMissingSmartscanFields(report?.reportID ?? '') && !ReportUtils.isSettled(report?.reportID)) { + if (ReportUtils.hasMissingSmartscanFields(report?.reportID ?? '', true) && !ReportUtils.isSettled(report?.reportID)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } - } else if (ReportUtils.hasSmartscanError(Object.values(reportActions ?? {}))) { + } else if (ReportUtils.hasSmartscanError(Object.values(reportActions ?? {}), true)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } // All error objects related to the report. Each object in the sources contains error messages keyed by microtime diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c9241054e74c..b2701569bd57 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2503,7 +2503,21 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio * Check if any of the transactions in the report has required missing fields * */ -function hasMissingSmartscanFields(iouReportID: string): boolean { +function hasMissingSmartscanFields(iouReportID: string, isLHNPreview = false): boolean { + if (isLHNPreview) { + const reportActions = Object.values(ReportActionsUtils.getAllReportActions(iouReportID)); + return reportActions.some((action) => { + if (!ReportActionsUtils.isMoneyRequestAction(action)) { + return false; + } + const transaction = getLinkedTransaction(action); + if (isEmptyObject(transaction)) { + return false; + } + console.log("22222222222", {transaction}) + return TransactionUtils.hasMissingSmartscanFields(transaction) && currentUserAccountID === action?.actorAccountID; + }); + } return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction)); } @@ -5401,13 +5415,13 @@ function canEditPolicyDescription(policy: OnyxEntry): boolean { /** * Checks if report action has error when smart scanning */ -function hasSmartscanError(reportActions: ReportAction[]) { +function hasSmartscanError(reportActions: ReportAction[], isLHNPreview = false) { return reportActions.some((action) => { if (!ReportActionsUtils.isSplitBillAction(action) && !ReportActionsUtils.isReportPreviewAction(action)) { return false; } const IOUReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); - const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID) && !isSettled(IOUReportID); + const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID, true) && !isSettled(IOUReportID); const transactionID = (action.originalMessage as IOUMessage).IOUTransactionID ?? '0'; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; const isSplitBillError = ReportActionsUtils.isSplitBillAction(action) && TransactionUtils.hasMissingSmartscanFields(transaction as Transaction); From fe5f752c317f83725fdbf07ead86600c426bc8b9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Apr 2024 04:15:23 +0700 Subject: [PATCH 0019/1233] fix lint --- src/libs/ReportUtils.ts | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b2701569bd57..e42b3add59e7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2499,6 +2499,22 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio return transactionsWithReceipts.every((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); } +/** + * Get the transactions related to a report preview with receipts + * Get the details linked to the IOU reportAction + * + * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. + */ +function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { + let transactionID = ''; + + if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? ''; + } + + return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; +} + /** * Check if any of the transactions in the report has required missing fields * @@ -2514,29 +2530,12 @@ function hasMissingSmartscanFields(iouReportID: string, isLHNPreview = false): b if (isEmptyObject(transaction)) { return false; } - console.log("22222222222", {transaction}) return TransactionUtils.hasMissingSmartscanFields(transaction) && currentUserAccountID === action?.actorAccountID; }); } return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction)); } -/** - * Get the transactions related to a report preview with receipts - * Get the details linked to the IOU reportAction - * - * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. - */ -function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { - let transactionID = ''; - - if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? ''; - } - - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; -} - /** * Given a parent IOU report action get report name for the LHN. */ @@ -5421,7 +5420,7 @@ function hasSmartscanError(reportActions: ReportAction[], isLHNPreview = false) return false; } const IOUReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); - const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID, true) && !isSettled(IOUReportID); + const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID, isLHNPreview) && !isSettled(IOUReportID); const transactionID = (action.originalMessage as IOUMessage).IOUTransactionID ?? '0'; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; const isSplitBillError = ReportActionsUtils.isSplitBillAction(action) && TransactionUtils.hasMissingSmartscanFields(transaction as Transaction); From c24f80ab3dbb226e9c1425a2e3e15af6c655cc11 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Apr 2024 13:29:44 +0700 Subject: [PATCH 0020/1233] reset code change --- .../AppNavigator/ReportScreenIDSetter.ts | 11 ------- src/pages/EditRequestPage.js | 32 ++----------------- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index c0f55b53e0c0..529f0f3d31a7 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -3,11 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import usePermissions from '@hooks/usePermissions'; -<<<<<<< HEAD -import Navigation from '@libs/Navigation/Navigation'; -======= import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; ->>>>>>> main import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/onyx'; @@ -48,13 +44,6 @@ const getLastAccessedReportID = ( policyID?: string, policyMemberAccountIDs?: number[], ): string | undefined => { - const currentRoute = Navigation.getActiveRoute(); - const {reportID} = ReportUtils.parseReportRouteParams(currentRoute); - - if (reportID) { - return reportID; - } - const lastReport = ReportUtils.findLastAccessedReport( reports, ignoreDefaultRooms, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 2438abdcda88..d3941dca044e 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -2,24 +2,19 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import categoryPropTypes from '@components/categoryPropTypes'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; import tagPropTypes from '@components/tagPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; -import withWindowDimensions from '@components/withWindowDimensions'; import compose from '@libs/compose'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; -import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import EditRequestReceiptPage from './EditRequestReceiptPage'; @@ -62,12 +57,6 @@ const propTypes = { /** Transaction that stores the request data */ transaction: transactionPropTypes, - - /** Indicates whether the app is loading initial data */ - isLoadingReportData: PropTypes.bool, - - /** Is the window width narrow, like on a mobile device */ - isSmallScreenWidth: PropTypes.bool.isRequired, }; const defaultProps = { @@ -77,7 +66,6 @@ const defaultProps = { policyTags: {}, parentReportActions: {}, transaction: {}, - isLoadingReportData: true, }; function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) { @@ -98,22 +86,10 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p // A flag for showing the tags page const shouldShowTags = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, transactionTag]); - const reportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID); - - // For small screen, we don't call openReport API when we go to a sub report page by deeplink - // So we need to call openReport here for small screen - useEffect(() => { - if (!isSmallScreenWidth || (!_.isEmpty(report) && !_.isEmpty(reportAction) && !_.isEmpty(transaction))) { - return; - } - Report.openReport(route.params.threadReportID); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSmallScreenWidth, route.params.threadReportID]); - // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. - if (isDataLoading || ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, fieldToEdit)) { + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, fieldToEdit)) { return; } @@ -121,7 +97,7 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [parentReportAction, fieldToEdit, transaction, isDataLoading]); + }, [parentReportAction, fieldToEdit]); const saveTag = useCallback( ({tag: newTag}) => { @@ -179,14 +155,10 @@ EditRequestPage.displayName = 'EditRequestPage'; EditRequestPage.propTypes = propTypes; EditRequestPage.defaultProps = defaultProps; export default compose( - withWindowDimensions, withOnyx({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, }, - isLoadingReportData: { - key: ONYXKEYS.IS_LOADING_REPORT_DATA, - }, }), // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ From 07a70afe9c66abcb237a256f3a02c39cfda89821 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Apr 2024 14:21:15 +0700 Subject: [PATCH 0021/1233] Fix not found page appear when openning edit page by deeplink --- .../iou/request/step/IOURequestStepDate.tsx | 6 ++--- .../step/IOURequestStepDescription.tsx | 6 ++--- .../step/withWritableReportOrNotFound.tsx | 26 +++++++++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDate.tsx b/src/pages/iou/request/step/IOURequestStepDate.tsx index 682204a4510f..2e5089657e64 100644 --- a/src/pages/iou/request/step/IOURequestStepDate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDate.tsx @@ -137,8 +137,8 @@ const IOURequestStepDateWithOnyx = withOnyx; + + isLoadingApp: OnyxEntry; }; type MoneyRequestRouteName = @@ -34,10 +38,25 @@ export default function , keyof WithWritableReportOrNotFoundOnyxProps>> { // eslint-disable-next-line rulesdir/no-negated-variables function WithWritableReportOrNotFound(props: TProps, ref: ForwardedRef) { - const {report = {reportID: ''}, route} = props; + const {report = {reportID: ''}, route, isLoadingApp = true} = props; const iouTypeParamIsInvalid = !Object.values(CONST.IOU.TYPE).includes(route.params?.iouType); + const isEditing = route.params?.action === CONST.IOU.ACTION.EDIT; const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); + useEffect(() => { + if (Boolean(report?.reportID) || !route.params.reportID) { + return; + } + + ReportActions.openReport(route.params.reportID); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (isEditing && isLoadingApp) { + return ; + } + if (iouTypeParamIsInvalid || !canUserPerformWriteAction) { return ; } @@ -57,6 +76,9 @@ export default function `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '0'}`, }, + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, })(forwardRef(WithWritableReportOrNotFound)); } From 894c310bcd560b6135f4d10246bfbd472d225128 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 11 Apr 2024 04:14:11 +0200 Subject: [PATCH 0022/1233] Preserve transactions amount in create IOU --- src/components/transactionPropTypes.js | 3 +++ src/libs/CurrencyUtils.ts | 20 +++++++++++++++--- src/libs/actions/IOU.ts | 6 +++--- src/pages/iou/request/IOURequestStartPage.js | 9 +++++++- .../iou/request/step/IOURequestStepAmount.js | 9 +++++++- .../iou/steps/MoneyRequestAmountForm.tsx | 21 +++++++++---------- src/types/onyx/Transaction.ts | 3 +++ tests/unit/CurrencyUtilsTest.ts | 20 +++++++++++++++--- 8 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index 7eb1b776358c..46d246948460 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -93,4 +93,7 @@ export default PropTypes.shape({ /** Server side errors keyed by microtime */ errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), + + /** Whether the original input should be shown */ + shouldShowOriginalAmount: PropTypes.bool, }); diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 95970d2a9582..4530dc105e03 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -87,9 +87,22 @@ function convertToBackendAmount(amountAsFloat: number): number { * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmount(amountAsInt: number): number { +function convertToFrontendAmountAsInteger(amountAsInt: number): number { return Math.trunc(amountAsInt) / 100.0; } + +/** + * Takes an amount in "cents" as an integer and converts it to a string amount used in the frontend. + * + * @note we do not support any currencies with more than two decimal places. + */ +function convertToFrontendAmountAsString(amountAsInt: number | null | undefined): string { + if (amountAsInt === null || amountAsInt === undefined) { + return ''; + } + return convertToFrontendAmountAsInteger(amountAsInt).toFixed(2); +} + /** * Given an amount in the "cents", convert it to a string for display in the UI. * The backend always handle things in "cents" (subunit equal to 1/100) @@ -98,7 +111,7 @@ function convertToFrontendAmount(amountAsInt: number): number { * @param currency - IOU currency */ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { - const convertedAmount = convertToFrontendAmount(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, @@ -139,7 +152,8 @@ export { getCurrencySymbol, isCurrencySymbolLTR, convertToBackendAmount, - convertToFrontendAmount, + convertToFrontendAmountAsInteger, + convertToFrontendAmountAsString, convertToDisplayString, convertAmountToDisplayString, isValidCurrencyCode, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index dab0eed1653a..8e3ead229a61 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -357,12 +357,12 @@ function startMoneyRequest(iouType: ValueOf, reportID: st } // eslint-disable-next-line @typescript-eslint/naming-convention -function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false) { +function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false, shouldShowOriginalAmount = false) { if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null, shouldShowOriginalAmount}); return; } - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, shouldShowOriginalAmount}); } // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index e9057fef9226..2a96e5b1e1eb 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -166,7 +166,14 @@ function IOURequestStartPage({ onTabSelected={resetIOUTypeIfChanged} tabBar={TabSelector} > - {() => } + + {() => ( + + )} + {() => } {shouldDisplayDistanceRequest && {() => }} diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index b392ee310205..0dd4d0247629 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,6 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import lodashIsEmpty from 'lodash/isEmpty'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -39,6 +40,9 @@ const propTypes = { /** The draft transaction object being modified in Onyx */ draftTransaction: transactionPropTypes, + + /** Whether the user input should be kept or not */ + shouldKeepUserInput: PropTypes.bool, }; const defaultProps = { @@ -46,6 +50,7 @@ const defaultProps = { transaction: {}, splitDraftTransaction: {}, draftTransaction: {}, + shouldKeepUserInput: false, }; function IOURequestStepAmount({ @@ -56,6 +61,7 @@ function IOURequestStepAmount({ transaction, splitDraftTransaction, draftTransaction, + shouldKeepUserInput, }) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -125,7 +131,7 @@ function IOURequestStepAmount({ isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); - IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true); + IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true, shouldKeepUserInput); if (backTo) { Navigation.goBack(backTo); @@ -183,6 +189,7 @@ function IOURequestStepAmount({ currency={currency} amount={Math.abs(transactionAmount)} ref={(e) => (textInput.current = e)} + shouldKeepUserInput={transaction.shouldShowOriginalAmount} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={saveAmountAndCurrency} selectedTab={iouRequestType} diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index c8b418f301c9..77a953dada90 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -49,6 +49,9 @@ type MoneyRequestAmountFormProps = { /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ selectedTab?: SelectedTabRequest; + + /** Whether the user input should be kept or not */ + shouldKeepUserInput?: boolean; }; type Selection = { @@ -66,7 +69,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => - isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount)); + isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmountAsInteger(Math.abs(taxAmount)); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; @@ -82,6 +85,7 @@ function MoneyRequestAmountForm( onCurrencyButtonPress, onSubmitButtonPress, selectedTab = CONST.TAB_REQUEST.MANUAL, + shouldKeepUserInput = false, }: MoneyRequestAmountFormProps, forwardedRef: ForwardedRef, ) { @@ -93,7 +97,7 @@ function MoneyRequestAmountForm( const isTaxAmountForm = Navigation.getActiveRoute().includes('taxAmount'); const decimals = CurrencyUtils.getCurrencyDecimals(currency); - const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmountAsString(amount) : ''; const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); const [formError, setFormError] = useState(''); @@ -135,7 +139,7 @@ function MoneyRequestAmountForm( }; const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount) : ''; setCurrentAmount(frontendAmount); setSelection({ start: frontendAmount.length, @@ -144,13 +148,13 @@ function MoneyRequestAmountForm( }, []); useEffect(() => { - if (!currency || typeof amount !== 'number') { + if (!currency || typeof amount !== 'number' || shouldKeepUserInput) { return; } initializeAmount(amount); // we want to re-initialize the state only when the selected tab or amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTab, amount]); + }, [selectedTab, amount, shouldKeepUserInput]); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -264,13 +268,8 @@ function MoneyRequestAmountForm( return; } - // Update display amount string post-edit to ensure consistency with backend amount - // Reference: https://github.com/Expensify/App/issues/30505 - const backendAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)); - initializeAmount(backendAmount); - onSubmitButtonPress({amount: currentAmount, currency}); - }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount]); + }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 281b6b4228ce..6b4bd1fc0d19 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -215,6 +215,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates transaction loading */ isLoading?: boolean; + + /** Whether the user input should be kept */ + shouldShowOriginalAmount?: boolean; }, keyof Comment >; diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index a1e4b03fa715..089cdf8426a8 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -105,15 +105,29 @@ describe('CurrencyUtils', () => { }); }); - describe('convertToFrontendAmount', () => { + describe('convertToFrontendAmountAsInteger', () => { test.each([ [2500, 25], [2550, 25.5], [25, 0.25], [2500, 25], [2500.5, 25], // The backend should never send a decimal .5 value - ])('Correctly converts %s to amount in units handled in frontend', (amount, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmount(amount)).toBe(expectedResult); + ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount)).toBe(expectedResult); + }); + }); + + describe('convertToFrontendAmountAsString', () => { + test.each([ + [2500, '25.00'], + [2550, '25.50'], + [25, '0.25'], + [2500.5, '25.00'], + [null, ''], + [undefined, ''], + [0, '0.00'], + ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsString(input)).toBe(expectedResult); }); }); From 23d62ea94184a054843ec5c3b20f273ceea6327c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 22:42:57 +0300 Subject: [PATCH 0023/1233] fix border radius --- src/components/ConfirmedRoute.tsx | 7 ++++++- src/styles/utils/index.ts | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index 596fc74dbbf6..fb0b4ddbb534 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -3,6 +3,7 @@ import type {ReactNode} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -38,6 +39,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction, isSmallIcon}: Confirmed const coordinates = route?.geometry?.coordinates ?? []; const theme = useTheme(); const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const getMarkerComponent = useCallback( (icon: IconAsset): ReactNode => ( @@ -105,7 +107,10 @@ function ConfirmedRoute({mapboxAccessToken, transaction, isSmallIcon}: Confirmed styleURL={CONST.MAPBOX.STYLE_URL} /> ) : ( - + ); } diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index e9efc84e8807..dd66a72faafa 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -419,6 +419,13 @@ function getBackgroundAndBorderStyle(backgroundColor: ColorValue | undefined): V }; } +/** + * Returns a style with the specified borderRadius + */ +function getBorderRadiusStyle(borderRadius: number): ViewStyle { + return {borderRadius}; +} + /** * Returns a style with the specified backgroundColor */ @@ -1108,6 +1115,7 @@ const staticStyleUtils = { getAvatarSize, getAvatarWidthStyle, getBackgroundAndBorderStyle, + getBorderRadiusStyle, getBackgroundColorStyle, getBackgroundColorWithOpacityStyle, getPaddingLeft, From b7cdaf83cbf947f9566005bca083f08e1a0047b9 Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Fri, 12 Apr 2024 04:04:11 +0700 Subject: [PATCH 0024/1233] feat: surfacing potential duplicates --- src/components/HoldBanner.tsx | 11 +++++-- src/components/MoneyRequestHeader.tsx | 29 +++++++++++++++-- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/libs/Permissions.ts | 2 +- src/libs/TransactionUtils.ts | 47 +++++++++++++++++++++++++-- src/types/onyx/Transaction.ts | 2 ++ 7 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/components/HoldBanner.tsx b/src/components/HoldBanner.tsx index af77d9076629..5376087ba849 100644 --- a/src/components/HoldBanner.tsx +++ b/src/components/HoldBanner.tsx @@ -5,14 +5,19 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Text from './Text'; import TextPill from './TextPill'; -function HoldBanner() { +type HoldBannerProps = { + isRequestDuplicate?: boolean; + shouldShowBorderBottom?: boolean; +}; + +function HoldBanner({isRequestDuplicate = false, shouldShowBorderBottom = false}: HoldBannerProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); return ( - + {translate('iou.hold')} - {translate('iou.requestOnHold')} + {isRequestDuplicate ? translate('iou.requestDuplicate') : translate('iou.requestOnHold')} ); } diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..6588b8167e62 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -16,6 +16,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; @@ -61,6 +62,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); const isApproved = ReportUtils.isReportApproved(moneyRequestReport); const isOnHold = TransactionUtils.isOnHold(transaction); + const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID ?? ''); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); // Only the requestor can take delete the request, admins can only edit it. @@ -178,7 +180,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, policy={policy} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} - /> + > + {isDuplicate && !isSmallScreenWidth && ( + - )} - {/** + + {isHidden ? translate('moderation.revealMessage') : translate('moderation.hideMessage')} + + + )} + {/** These are the actionable buttons that appear at the bottom of a Concierge message for example: Invite a user mentioned but not a member of the room https://github.com/Expensify/App/issues/32741 */} - {actionableItemButtons.length > 0 && ( - - )} - - ) : ( - - )} + {actionableItemButtons.length > 0 && ( + + )} + + ) : ( + + )} + ); } diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index 82b49d1e260c..e6dbd760e5af 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -6,13 +6,14 @@ import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type ReportAttachmentsProps = StackScreenProps; +type ReportAttachmentsProps = StackScreenProps; function ReportAttachments({route}: ReportAttachmentsProps) { - const reportID = route.params.reportID; + const reportID = route.params.id; const report = ReportUtils.getReport(reportID); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource @@ -20,7 +21,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) { const onCarouselAttachmentChange = useCallback( (attachment: Attachment) => { - const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, String(attachment.source)); + const routeToNavigate = ROUTES.ATTACHMENTS.getRoute(reportID, CONST.ATTACHMENT_TYPE.REPORT, String(attachment.source)); Navigation.navigate(routeToNavigate); }, [reportID], From 6093f69f680f2dfcc1342015a9d3c308d4a0e98e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 25 Apr 2024 06:58:11 +0700 Subject: [PATCH 0053/1233] fix update center button --- assets/images/crosshair.svg | 23 +++++++++ src/components/Icon/Expensicons.ts | 2 + src/components/MapView/MapView.tsx | 54 +++++++++++++++++++--- src/components/MapView/MapView.website.tsx | 52 ++++++++++++++++++--- 4 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 assets/images/crosshair.svg diff --git a/assets/images/crosshair.svg b/assets/images/crosshair.svg new file mode 100644 index 000000000000..357faab49178 --- /dev/null +++ b/assets/images/crosshair.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 877e4972a3ec..8e3ec51ff265 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -42,6 +42,7 @@ import Concierge from '@assets/images/concierge.svg'; import Connect from '@assets/images/connect.svg'; import Copy from '@assets/images/copy.svg'; import CreditCard from '@assets/images/creditcard.svg'; +import Crosshair from '@assets/images/crosshair.svg'; import DocumentPlus from '@assets/images/document-plus.svg'; import DocumentSlash from '@assets/images/document-slash.svg'; import Document from '@assets/images/document.svg'; @@ -197,6 +198,7 @@ export { Concierge, ConciergeAvatar, Connect, + Crosshair, Copy, CreditCard, DeletedRoomAvatar, diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 28ae3b6e354f..8f8db7f7f34e 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -4,11 +4,15 @@ import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps'; import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import Button from '@components/Button'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Icon from '@components/Icon'; +import {PressableWithoutFeedback} from '@components/Pressable'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import setUserLocation from '@libs/actions/UserLocation'; import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; @@ -26,6 +30,14 @@ const MapView = forwardRef( const {isOffline} = useNetwork(); const {translate} = useLocalize(); const styles = useThemeStyles(); + const theme = useTheme(); + const centerButtonOpacity = useSharedValue(1); + const [isMapCentered, setIsMapCentered] = useState(true); + const centerButtonAnimatedStyle = useAnimatedStyle(() => { + return { + opacity: centerButtonOpacity.value, + }; + }); const cameraRef = useRef(null); const [isIdle, setIsIdle] = useState(false); @@ -148,6 +160,9 @@ const MapView = forwardRef( if (directionCoordinates && directionCoordinates.length > 1) { const {southWest, northEast} = utils.getBounds(waypoints?.map((waypoint) => waypoint.coordinate) ?? [], directionCoordinates); cameraRef.current?.fitBounds(southWest, northEast, mapPadding, CONST.MAPBOX.ANIMATION_DURATION_ON_CENTER_ME); + centerButtonOpacity.value = withTiming(0, {duration: 1000}, () => { + setIsMapCentered(true); + }); return; } cameraRef?.current?.setCamera({ @@ -155,10 +170,23 @@ const MapView = forwardRef( centerCoordinate: [currentPosition?.longitude ?? 0, currentPosition?.latitude ?? 0], animationDuration: CONST.MAPBOX.ANIMATION_DURATION_ON_CENTER_ME, }); + centerButtonOpacity.value = withTiming(0, {duration: 1000}, () => { + setIsMapCentered(true); + }); }, [directionCoordinates, currentPosition, mapPadding, waypoints]); + + const onRegionDidChange = useCallback(() => { + if (!isMapCentered) { + return; + } + setIsMapCentered(false); + centerButtonOpacity.value = 1; + }, [isMapCentered]); + return !isOffline && Boolean(accessToken) && Boolean(currentPosition) ? ( ( {directionCoordinates && } -