From 780477c067a00c8b2224671bf8fce05205a8c170 Mon Sep 17 00:00:00 2001 From: Pierre Michel Date: Mon, 12 Jun 2023 06:53:22 -0600 Subject: [PATCH 001/106] Make part of MoneyRequestHeader scrollable, the top of the header stays fixed, the other part is moved as a footer of the InvertedFlatList which makes it a header Signed-off-by: Pierre Michel --- src/components/MoneyRequestHeader.js | 36 ++---- src/components/MoneyRequestTopHeader.js | 121 +++++++++++++++++++++ src/pages/home/ReportScreen.js | 4 +- src/pages/home/report/ReportActionsList.js | 30 ++++- src/pages/home/report/ReportActionsView.js | 1 + src/styles/styles.js | 4 + src/styles/utilities/spacing.js | 4 + 7 files changed, 170 insertions(+), 30 deletions(-) create mode 100644 src/components/MoneyRequestTopHeader.js diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 600a96aede18..7df7fd3f42c0 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -36,11 +36,14 @@ const propTypes = { /** The expense report or iou report (only will have a value if this is a transaction thread) */ parentReport: iouReportPropTypes, - /** The policies which the user has access to and which the report could be tied to */ - policies: PropTypes.shape({ - /** Name of the policy */ + /** The policy object for the current route */ + policy: PropTypes.shape({ + /** The name of the policy */ name: PropTypes.string, - }).isRequired, + + /** The URL for the policy avatar */ + avatar: PropTypes.string, + }), /** The chat report this report is linked to */ chatReport: reportPropTypes, @@ -80,37 +83,18 @@ const MoneyRequestHeader = (props) => { const moneyRequestReport = props.isSingleTransactionView ? props.parentReport : props.report; const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isExpenseReport = ReportUtils.isExpenseReport(moneyRequestReport); - const payeeName = isExpenseReport ? ReportUtils.getPolicyName(moneyRequestReport, props.policies) : ReportUtils.getDisplayNameForParticipant(moneyRequestReport.managerEmail); + const payeeName = isExpenseReport ? ReportUtils.getPolicyName(moneyRequestReport) : ReportUtils.getDisplayNameForParticipant(moneyRequestReport.managerEmail); const payeeAvatar = isExpenseReport ? ReportUtils.getWorkspaceAvatar(moneyRequestReport) : UserUtils.getAvatar(lodashGet(props.personalDetails, [moneyRequestReport.managerEmail, 'avatar']), moneyRequestReport.managerEmail); - const policy = props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`]; + const isPayer = - Policy.isAdminOfFreePolicy([policy]) || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(props.session, 'email', null) === moneyRequestReport.managerEmail); + Policy.isAdminOfFreePolicy([props.policy]) || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(props.session, 'email', null) === moneyRequestReport.managerEmail); const shouldShowSettlementButton = !isSettled && !props.isSingleTransactionView && isPayer; const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); const shouldShowPaypal = Boolean(lodashGet(props.personalDetails, [moneyRequestReport.managerEmail, 'payPalMeAddress'])); return ( - {}, - }, - ]} - threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(props.windowWidth)} - report={props.report} - parentReport={moneyRequestReport} - policies={props.policies} - personalDetails={props.personalDetails} - shouldShowBackButton={props.isSmallScreenWidth} - onBackButtonPress={() => Navigation.goBack(ROUTES.HOME)} - /> {props.translate('common.to')} diff --git a/src/components/MoneyRequestTopHeader.js b/src/components/MoneyRequestTopHeader.js new file mode 100644 index 000000000000..5513775089d8 --- /dev/null +++ b/src/components/MoneyRequestTopHeader.js @@ -0,0 +1,121 @@ +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import iouReportPropTypes from '../pages/iouReportPropTypes'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import * as ReportUtils from '../libs/ReportUtils'; +import * as Expensicons from './Icon/Expensicons'; +import Text from './Text'; +import participantPropTypes from './participantPropTypes'; +import Avatar from './Avatar'; +import styles from '../styles/styles'; +import themeColors from '../styles/themes/default'; +import CONST from '../CONST'; +import withWindowDimensions from './withWindowDimensions'; +import compose from '../libs/compose'; +import Navigation from '../libs/Navigation/Navigation'; +import ROUTES from '../ROUTES'; +import Icon from './Icon'; +import SettlementButton from './SettlementButton'; +import * as Policy from '../libs/actions/Policy'; +import ONYXKEYS from '../ONYXKEYS'; +import * as IOU from '../libs/actions/IOU'; +import * as CurrencyUtils from '../libs/CurrencyUtils'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import DateUtils from '../libs/DateUtils'; +import reportPropTypes from '../pages/reportPropTypes'; +import * as UserUtils from '../libs/UserUtils'; + +const propTypes = { + /** The report currently being looked at */ + report: iouReportPropTypes.isRequired, + + /** The expense report or iou report (only will have a value if this is a transaction thread) */ + parentReport: iouReportPropTypes, + + /** The policies which the user has access to */ + policies: PropTypes.objectOf( + PropTypes.shape({ + /** The policy name */ + name: PropTypes.string, + + /** The type of the policy */ + type: PropTypes.string, + }), + ), + + + /** Personal details so we can get the ones for the report participants */ + personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, + + /** Whether we're viewing a report with a single transaction in it */ + isSingleTransactionView: PropTypes.bool, + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user email */ + email: PropTypes.string, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + isSingleTransactionView: false, + session: { + email: null, + }, + parentReport: {}, +}; + +const MoneyRequestTopHeader = (props) => { + const moneyRequestReport = props.isSingleTransactionView ? props.parentReport : props.report; + const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); + const policy = props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`]; + const isPayer = + Policy.isAdminOfFreePolicy([policy]) || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(props.session, 'email', null) === moneyRequestReport.managerEmail); + return ( + + + {}, + }, + ]} + threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(props.windowWidth)} + report={props.report} + parentReport={moneyRequestReport} + policies={props.policies} + personalDetails={props.personalDetails} + shouldShowBackButton={props.isSmallScreenWidth} + onBackButtonPress={() => Navigation.goBack(ROUTES.HOME)} + /> + + ); +}; + +MoneyRequestTopHeader.displayName = 'MoneyRequestTopHeader'; +MoneyRequestTopHeader.propTypes = propTypes; +MoneyRequestTopHeader.defaultProps = defaultProps; + +export default compose( + withWindowDimensions, + withLocalize, + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + parentReport: { + key: (props) => `${ONYXKEYS.COLLECTION.REPORT}${props.report.parentReportID}`, + }, + }), +)(MoneyRequestTopHeader); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 652ea5eb5d2c..5fa8c622c665 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -36,7 +36,7 @@ import getIsReportFullyVisible from '../../libs/getIsReportFullyVisible'; import EmojiPicker from '../../components/EmojiPicker/EmojiPicker'; import * as EmojiPickerAction from '../../libs/actions/EmojiPickerAction'; import TaskHeader from '../../components/TaskHeader'; -import MoneyRequestHeader from '../../components/MoneyRequestHeader'; +import MoneyRequestTopHeader from '../../components/MoneyRequestTopHeader'; import withNavigation, {withNavigationPropTypes} from '../../components/withNavigation'; import * as ComposerActions from '../../libs/actions/Composer'; @@ -263,7 +263,7 @@ class ReportScreen extends React.Component { shouldShowErrorMessages={false} > {ReportUtils.isMoneyRequestReport(this.props.report) || isSingleTransactionView ? ( - { // To notify there something changes we can use extraData prop to flatlist const extraData = [props.isSmallScreenWidth ? props.newMarkerReportActionID : undefined, ReportUtils.isArchivedRoom(props.report)]; const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(props.personalDetails, props.report, props.currentUserPersonalDetails.login); + + const parentReportAction = ReportActionsUtils.getParentReportAction(props.report); + const isSingleTransactionView = ReportActionsUtils.isTransactionThread(parentReportAction); + const showMoneyRequestHeader = ReportUtils.isMoneyRequestReport(props.report) || isSingleTransactionView; return ( { ref={ReportScrollManager.flatListRef} data={props.sortedReportActions} renderItem={renderItem} - contentContainerStyle={[styles.chatContentScrollView, shouldShowReportRecipientLocalTime && styles.pt0]} + contentContainerStyle={[styles.chatContentScrollView, shouldShowReportRecipientLocalTime && styles.pt0, showMoneyRequestHeader && styles.pb0]} keyExtractor={keyExtractor} initialRowHeight={32} initialNumToRender={calculateInitialNumToRender()} onEndReached={props.loadMoreChats} onEndReachedThreshold={0.75} + ListFooterComponentStyle={showMoneyRequestHeader && styles.chatFooterAtTheTop} + ListFooterComponent={() => { if (props.report.isLoadingMoreReportActions) { return ; @@ -178,7 +194,17 @@ const ReportActionsList = (props) => { /> ); } - + if(showMoneyRequestHeader) { + return ( + + ); + } return null; }} keyboardShouldPersistTaps="handled" diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 6c7cf10da781..ce377f4ecf1d 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -352,6 +352,7 @@ class ReportActionsView extends React.Component { isLoadingMoreReportActions={this.props.report.isLoadingMoreReportActions} loadMoreChats={this.loadMoreChats} newMarkerReportActionID={this.state.newMarkerReportActionID} + policy={this.props.policy} /> Date: Thu, 15 Jun 2023 16:15:58 +0700 Subject: [PATCH 002/106] fix: 20517 quickly clicking on different chats on LHN it opens one by one --- src/pages/home/sidebar/SidebarLinks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 9b502193d704..07c8ca4c1185 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -143,7 +143,7 @@ class SidebarLinks extends React.Component { * @param {String} option.reportID */ showReportPage(option) { - if (this.props.isCreateMenuOpen) { + if (this.props.isCreateMenuOpen || (this.props.isSmallScreenWidth && Navigation.getTopmostReportId())) { // Prevent opening Report page when click LHN row quickly after clicking FAB icon return; } From 01de9c5ab622e9bb5c5fbb17b12d3a64b4272b70 Mon Sep 17 00:00:00 2001 From: BhuvaneshPatil Date: Sat, 17 Jun 2023 16:05:03 +0530 Subject: [PATCH 003/106] migrating MoneyRequestAmountPage to functional component --- src/pages/iou/steps/MoneyRequestAmountPage.js | 402 ++++++++---------- 1 file changed, 181 insertions(+), 221 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestAmountPage.js b/src/pages/iou/steps/MoneyRequestAmountPage.js index 39495c6a5f2c..986192c547ae 100755 --- a/src/pages/iou/steps/MoneyRequestAmountPage.js +++ b/src/pages/iou/steps/MoneyRequestAmountPage.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, {useEffect, useState, useRef} from 'react'; import {View, InteractionManager} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; import _ from 'underscore'; +import {useFocusEffect} from '@react-navigation/native'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; import BigNumberPad from '../../../components/BigNumberPad'; @@ -46,54 +47,17 @@ const propTypes = { const defaultProps = { iou: {}, }; -class MoneyRequestAmountPage extends React.Component { - constructor(props) { - super(props); - - this.updateAmountNumberPad = this.updateAmountNumberPad.bind(this); - this.updateLongPressHandlerState = this.updateLongPressHandlerState.bind(this); - this.updateAmount = this.updateAmount.bind(this); - this.stripCommaFromAmount = this.stripCommaFromAmount.bind(this); - this.stripSpacesFromAmount = this.stripSpacesFromAmount.bind(this); - this.focusTextInput = this.focusTextInput.bind(this); - this.navigateToCurrencySelectionPage = this.navigateToCurrencySelectionPage.bind(this); - this.amountViewID = 'amountView'; - this.numPadContainerViewID = 'numPadContainerView'; - this.numPadViewID = 'numPadView'; - - const selectedAmountAsString = props.selectedAmount ? props.selectedAmount.toString() : ''; - this.state = { - amount: selectedAmountAsString, - selectedCurrencyCode: _.isUndefined(props.iou.selectedCurrencyCode) ? CONST.CURRENCY.USD : props.iou.selectedCurrencyCode, - shouldUpdateSelection: true, - selection: { - start: selectedAmountAsString.length, - end: selectedAmountAsString.length, - }, - }; - } - - componentDidMount() { - this.focusTextInput(); - - // Focus automatically after navigating back from currency selector - this.unsubscribeNavFocus = this.props.navigation.addListener('focus', () => { - this.focusTextInput(); - this.getCurrencyFromRouteParams(); - }); - } - - componentDidUpdate(prevProps) { - if (prevProps.iou.selectedCurrencyCode === this.props.iou.selectedCurrencyCode) { - return; - } - - this.setState({selectedCurrencyCode: this.props.iou.selectedCurrencyCode}); - } - - componentWillUnmount() { - this.unsubscribeNavFocus(); - } +function MoneyRequestAmountPage(props) { + const amountViewID = 'amountView'; + const numPadContainerViewID = 'numPadContainerView'; + const numPadViewID = 'numPadView'; + const selectedAmountAsString = props.selectedAmount ? props.selectedAmount.toString() : ''; + const textInput = useRef(null); + + const [amount, setAmount] = useState(selectedAmountAsString); + const [selectedCurrencyCode, setSelectedCurrencyCode] = useState(() => (_.isUndefined(props.iou.selectedCurrencyCode) ? CONST.CURRENCY.USD : props.iou.selectedCurrencyCode)); + const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); + const [selection, setSelection] = useState({start: selectedAmountAsString.length, end: selectedAmountAsString.length}); /** * Event occurs when a user presses a mouse button over an DOM element. @@ -101,23 +65,26 @@ class MoneyRequestAmountPage extends React.Component { * @param {Event} event * @param {Array} nativeIds */ - onMouseDown(event, nativeIds) { + const onMouseDown = (event, nativeIds) => { const relatedTargetId = lodashGet(event, 'nativeEvent.target.id'); if (!_.contains(nativeIds, relatedTargetId)) { return; } event.preventDefault(); - if (!this.textInput.isFocused()) { - this.textInput.focus(); + if (!textInput.current.isFocused()) { + textInput.current.focus(); } - } + }; - getCurrencyFromRouteParams() { - const selectedCurrencyCode = lodashGet(this.props.route.params, 'currency', ''); - if (selectedCurrencyCode !== '') { - this.setState({selectedCurrencyCode}); + const getCurrencyFromRouteParams = () => { + if (props.iou.selectedCurrencyCode === lodashGet(props.route.params, 'currency', '')) { + return; + } + const newSelectedCurrencyCode = lodashGet(props.route.params, 'currency', ''); + if (newSelectedCurrencyCode !== '') { + setSelectedCurrencyCode(newSelectedCurrencyCode); } - } + }; /** * Returns the new selection object based on the updated amount's length @@ -127,55 +94,68 @@ class MoneyRequestAmountPage extends React.Component { * @param {Number} newLength * @returns {Object} */ - getNewSelection(oldSelection, prevLength, newLength) { + const getNewSelection = (oldSelection, prevLength, newLength) => { const cursorPosition = oldSelection.end + (newLength - prevLength); return {start: cursorPosition, end: cursorPosition}; - } - - /** - * Returns new state object if the updated amount is valid - * - * @param {Object} prevState - * @param {String} newAmount - Changed amount from user input - * @returns {Object} - */ - getNewState(prevState, newAmount) { - // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value - // More info: https://github.com/Expensify/App/issues/16974 - const newAmountWithoutSpaces = this.stripSpacesFromAmount(newAmount); - if (!this.validateAmount(newAmountWithoutSpaces)) { - // Use a shallow copy of selection to trigger setSelection - // More info: https://github.com/Expensify/App/issues/16385 - return {amount: prevState.amount, selection: {...prevState.selection}}; - } - const selection = this.getNewSelection(prevState.selection, prevState.amount.length, newAmountWithoutSpaces.length); - return {amount: this.stripCommaFromAmount(newAmountWithoutSpaces), selection}; - } + }; /** * Focus text input */ - focusTextInput() { + const focusTextInput = () => { // Component may not initialized due to navigation transitions // Wait until interactions are complete before trying to focus InteractionManager.runAfterInteractions(() => { // Focus text input - if (!this.textInput) { + if (!textInput.current) { return; } - this.textInput.focus(); + textInput.current.focus(); }); - } + }; + + useEffect(() => { + focusTextInput(); + }, []); + + useFocusEffect(() => { + focusTextInput(); + getCurrencyFromRouteParams(); + }); + + /** + * Strip comma from the amount + * + * @param {String} newAmount + * @returns {String} + */ + const stripCommaFromAmount = (newAmount) => newAmount.replace(/,/g, ''); + + /** + * Strip spaces from the amount + * + * @param {String} newAmount + * @returns {String} + */ + const stripSpacesFromAmount = (newAmount) => newAmount.replace(/\s+/g, ''); + + /** + * Adds a leading zero to the amount if user entered just the decimal separator + * + * @param {String} newAmount - Changed amount from user input + * @returns {String} + */ + const addLeadingZero = (newAmount) => (newAmount === '.' ? '0.' : newAmount); /** - * @param {String} amount + * @param {String} newAmount * @returns {Number} */ - calculateAmountLength(amount) { - const leadingZeroes = amount.match(/^0+/); + const calculateAmountLength = (newAmount) => { + const leadingZeroes = newAmount.match(/^0+/); const leadingZeroesLength = lodashGet(leadingZeroes, '[0].length', 0); - const absAmount = parseFloat((this.stripCommaFromAmount(amount) * 100).toFixed(2)).toString(); + const absAmount = parseFloat((stripCommaFromAmount(newAmount) * 100).toFixed(2)).toString(); // The following logic will prevent users from pasting an amount that is excessively long in length, // which would result in the 'absAmount' value being expressed in scientific notation or becoming infinity. @@ -184,52 +164,43 @@ class MoneyRequestAmountPage extends React.Component { } /* - Return the sum of leading zeroes length and absolute amount length(including fraction digits). - When the absolute amount is 0, add 2 to the leading zeroes length to represent fraction digits. - */ + Return the sum of leading zeroes length and absolute amount length(including fraction digits). + When the absolute amount is 0, add 2 to the leading zeroes length to represent fraction digits. + */ return leadingZeroesLength + (absAmount === '0' ? 2 : absAmount.length); - } + }; /** * Check if amount is a decimal up to 3 digits * - * @param {String} amount + * @param {String} newAmount * @returns {Boolean} */ - validateAmount(amount) { + const validateAmount = (newAmount) => { const decimalNumberRegex = new RegExp(/^\d+(,\d+)*(\.\d{0,2})?$/, 'i'); - return amount === '' || (decimalNumberRegex.test(amount) && this.calculateAmountLength(amount) <= CONST.IOU.AMOUNT_MAX_LENGTH); - } + return newAmount === '' || (decimalNumberRegex.test(newAmount) && calculateAmountLength(newAmount) <= CONST.IOU.AMOUNT_MAX_LENGTH); + }; /** - * Strip comma from the amount - * - * @param {String} amount - * @returns {String} - */ - stripCommaFromAmount(amount) { - return amount.replace(/,/g, ''); - } - - /** - * Strip spaces from the amount - * - * @param {String} amount - * @returns {String} - */ - stripSpacesFromAmount(amount) { - return amount.replace(/\s+/g, ''); - } - - /** - * Adds a leading zero to the amount if user entered just the decimal separator - * - * @param {String} amount - Changed amount from user input - * @returns {String} + * Returns new state object if the updated amount is valid + * @param {String} newAmount - Changed amount from user input */ - addLeadingZero(amount) { - return amount === '.' ? '0.' : amount; - } + const setNewState = (newAmount) => { + // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value + // More info: https://github.com/Expensify/App/issues/16974 + const newAmountWithoutSpaces = stripSpacesFromAmount(newAmount); + if (!validateAmount(newAmountWithoutSpaces)) { + // Use a shallow copy of selection to trigger setSelection + // More info: https://github.com/Expensify/App/issues/16385 + setAmount(amount); + setSelection(selection); + // return {amount: prevState.amount, selection: {...prevState.selection}}; + } + const newSelection = getNewSelection(selection, amount.length, newAmountWithoutSpaces.length); + setAmount(stripCommaFromAmount(newAmountWithoutSpaces)); + setSelection(newSelection); + // return {amount: stripCommaFromAmount(newAmountWithoutSpaces), selection}; + }; /** * Update amount with number or Backspace pressed for BigNumberPad. @@ -237,64 +208,45 @@ class MoneyRequestAmountPage extends React.Component { * * @param {String} key */ - updateAmountNumberPad(key) { - if (this.state.shouldUpdateSelection && !this.textInput.isFocused()) { - this.textInput.focus(); + const updateAmountNumberPad = (key) => { + if (shouldUpdateSelection && !textInput.current.isFocused()) { + textInput.current.focus(); } - // Backspace button is pressed if (key === '<' || key === 'Backspace') { - if (this.state.amount.length > 0) { - this.setState((prevState) => { - const selectionStart = prevState.selection.start === prevState.selection.end ? prevState.selection.start - 1 : prevState.selection.start; - const amount = `${prevState.amount.substring(0, selectionStart)}${prevState.amount.substring(prevState.selection.end)}`; - return this.getNewState(prevState, amount); - }); + if (amount.length > 0) { + const selectionStart = selection.start === selection.end ? selection.start - 1 : selection.start; + const newAmount = `${amount.substring(0, selectionStart)}${amount.substring(selection.end)}`; + setNewState(newAmount); } return; } - - this.setState((prevState) => { - const amount = this.addLeadingZero(`${prevState.amount.substring(0, prevState.selection.start)}${key}${prevState.amount.substring(prevState.selection.end)}`); - return this.getNewState(prevState, amount); - }); - } + const newAmount = addLeadingZero(`${amount.substring(0, selection.start)}${key}${amount.substring(selection.end)}`); + setNewState(newAmount); + }; /** * Update long press value, to remove items pressing on < * * @param {Boolean} value - Changed text from user input */ - updateLongPressHandlerState(value) { - this.setState({shouldUpdateSelection: !value}); - if (!value && !this.textInput.isFocused()) { - this.textInput.focus(); + const updateLongPressHandlerState = (value) => { + setShouldUpdateSelection(!value); + if (!value && !textInput.current.isFocused()) { + textInput.current.focus(); } - } - - /** - * Update amount on amount change - * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit - * - * @param {String} text - Changed text from user input - */ - updateAmount(text) { - this.setState((prevState) => { - const amount = this.addLeadingZero(this.replaceAllDigits(text, this.props.fromLocaleDigit)); - return this.getNewState(prevState, amount); - }); - } + }; /** * Replaces each character by calling `convertFn`. If `convertFn` throws an error, then * the original character will be preserved. * * @param {String} text - * @param {Function} convertFn - `this.props.fromLocaleDigit` or `this.props.toLocaleDigit` + * @param {Function} convertFn - `props.fromLocaleDigit` or `props.toLocaleDigit` * @returns {String} */ - replaceAllDigits(text, convertFn) { - return _.chain([...text]) + const replaceAllDigits = (text, convertFn) => + _.chain([...text]) .map((char) => { try { return convertFn(char); @@ -304,73 +256,81 @@ class MoneyRequestAmountPage extends React.Component { }) .join('') .value(); - } - navigateToCurrencySelectionPage() { + /** + * Update amount on amount change + * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit + * + * @param {String} text - Changed text from user input + */ + const updateAmount = (text) => { + const newAmount = addLeadingZero(replaceAllDigits(text, props.fromLocaleDigit)); + setNewState(newAmount); + }; + + const navigateToCurrencySelectionPage = () => { // Remove query from the route and encode it. const activeRoute = encodeURIComponent(Navigation.getActiveRoute().replace(/\?.*/, '')); - if (this.props.hasMultipleParticipants) { - return Navigation.navigate(ROUTES.getIouBillCurrencyRoute(this.props.reportID, this.state.selectedCurrencyCode, activeRoute)); + if (props.hasMultipleParticipants) { + return Navigation.navigate(ROUTES.getIouBillCurrencyRoute(props.reportID, selectedCurrencyCode, activeRoute)); } - if (this.props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND) { - return Navigation.navigate(ROUTES.getIouSendCurrencyRoute(this.props.reportID, this.state.selectedCurrencyCode, activeRoute)); + if (props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND) { + return Navigation.navigate(ROUTES.getIouSendCurrencyRoute(props.reportID, selectedCurrencyCode, activeRoute)); } - return Navigation.navigate(ROUTES.getIouRequestCurrencyRoute(this.props.reportID, this.state.selectedCurrencyCode, activeRoute)); - } - - render() { - const formattedAmount = this.replaceAllDigits(this.state.amount, this.props.toLocaleDigit); - - return ( - <> - this.onMouseDown(event, [this.amountViewID])} - style={[styles.flex1, styles.flexRow, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]} - > - (this.textInput = el)} - selectedCurrencyCode={this.state.selectedCurrencyCode} - selection={this.state.selection} - onSelectionChange={(e) => { - if (!this.state.shouldUpdateSelection) { - return; - } - this.setState({selection: e.nativeEvent.selection}); - }} - /> - - this.onMouseDown(event, [this.numPadContainerViewID, this.numPadViewID])} - style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper]} - nativeID={this.numPadContainerViewID} - > - {DeviceCapabilities.canUseTouchScreen() ? ( - - ) : ( - - )} - -