From 8496d9904e37d988c484da17e1c54c7107a4cd34 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 13 Jul 2023 16:20:23 +0200 Subject: [PATCH 001/282] Handle onBlur in MAgic Code Input component --- src/components/MagicCodeInput.js | 95 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index f27b1a2fd0c5..42c517aaecdc 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,5 +1,5 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleSheet, View, Pressable} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../styles/styles'; @@ -96,19 +96,21 @@ const composeToString = (value) => _.map(value, (v) => (v === undefined || v === const getInputPlaceholderSlots = (length) => Array.from(Array(length).keys()); function MagicCodeInput(props) { - const inputRefs = useRef([]); + const inputRefs = useRef(); const [input, setInput] = useState(''); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); + const shouldFocusLast = useRef(false); + const lastFocusedIndex = useRef(0); const blurMagicCodeInput = () => { - inputRefs.current[editIndex].blur(); + inputRefs.current.blur(); setFocusedIndex(undefined); }; const focusMagicCodeInput = () => { setFocusedIndex(0); - inputRefs.current[0].focus(); + inputRefs.current.focus(); }; useImperativeHandle(props.innerRef, () => ({ @@ -123,7 +125,7 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(0); setEditIndex(0); - inputRefs.current[0].focus(); + inputRefs.current.focus(); props.onChangeText(''); }, blur() { @@ -158,9 +160,9 @@ function MagicCodeInput(props) { let focusTimeout = null; if (props.shouldDelayFocus) { - focusTimeout = setTimeout(() => inputRefs.current[0].focus(), CONST.ANIMATED_TRANSITION); + focusTimeout = setTimeout(() => inputRefs.current.focus(), CONST.ANIMATED_TRANSITION); } else { - inputRefs.current[0].focus(); + inputRefs.current.focus(); } return () => { @@ -180,7 +182,13 @@ function MagicCodeInput(props) { * @param {Number} index */ const onFocus = (event) => { + if (shouldFocusLast.current) { + setInput(''); + setFocusedIndex(lastFocusedIndex.current); + setEditIndex(lastFocusedIndex.current); + } event.preventDefault(); + shouldFocusLast.current = true; }; /** @@ -190,8 +198,9 @@ function MagicCodeInput(props) { * @param {Object} event * @param {Number} index */ - const onPress = (event, index) => { - event.preventDefault(); + const onPress = (index) => { + shouldFocusLast.current = false; + inputRefs.current.focus(); setInput(''); setFocusedIndex(index); setEditIndex(index); @@ -273,7 +282,7 @@ function MagicCodeInput(props) { props.onChangeText(composeToString(numbers)); if (!_.isUndefined(newFocusedIndex)) { - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } } if (keyValue === 'ArrowLeft' && !_.isUndefined(focusedIndex)) { @@ -281,13 +290,13 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } else if (keyValue === 'ArrowRight' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.min(focusedIndex + 1, props.maxLength - 1); setInput(''); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } else if (keyValue === 'Enter') { // We should prevent users from submitting when it's offline. if (props.network.isOffline) { @@ -306,9 +315,37 @@ function MagicCodeInput(props) { return ( <> + + (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + console.log('blur', focusedIndex); + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + /> + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - onPress(index)} style={[styles.w15]} > {decomposeString(props.value, props.maxLength)[index] || ''} - - (inputRefs.current[index] = ref)} - autoFocus={index === 0 && props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={index === 0 ? props.autoComplete : 'off'} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - // Do not run when the event comes from an input that is - // not currently being responsible for the input, this is - // necessary to avoid calls when the input changes due to - // deleted characters. Only happens in mobile. - if (index !== editIndex || _.isUndefined(focusedIndex)) { - return; - } - onChangeText(value); - }} - onKeyPress={onKeyPress} - onPress={(event) => onPress(event, index)} - onFocus={onFocus} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - /> - - + ))} {!_.isEmpty(props.errorText) && ( From 077170b6ca6511ba54589516a060943c386e9144 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 19 Jul 2023 22:05:11 +0200 Subject: [PATCH 002/282] Add TapGestureHandler --- src/components/MagicCodeInput.js | 68 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 63b56db5aec0..779b94919bf2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -13,6 +13,7 @@ import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; +import { TapGestureHandler } from 'react-native-gesture-handler'; const propTypes = { /** Information about the network */ @@ -101,6 +102,7 @@ function MagicCodeInput(props) { const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); const shouldFocusLast = useRef(false); + const inputWidth = useRef(0); const lastFocusedIndex = useRef(0); const blurMagicCodeInput = () => { @@ -317,39 +319,43 @@ function MagicCodeInput(props) { return ( <> - - (inputRefs.current = ref)} - autoFocus={props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={props.autoComplete} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - onChangeText(value); + + { + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))) }} - onKeyPress={onKeyPress} - onFocus={onFocus} - onBlur={() => { - console.log('blur', focusedIndex); - lastFocusedIndex.current = focusedIndex; - setFocusedIndex(undefined); - }} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - /> + > + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + /> + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - onPress(index)} - style={[styles.w15]} - > + {decomposeString(props.value, props.maxLength)[index] || ''} - + ))} {!_.isEmpty(props.errorText) && ( From 9be387c5be6f38ba615853c2f971be3491f32868 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Fri, 21 Jul 2023 17:50:23 +0200 Subject: [PATCH 003/282] Fix backspace on phones --- src/components/MagicCodeInput.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 779b94919bf2..9414491bbeae 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -236,7 +236,8 @@ function MagicCodeInput(props) { numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, props.maxLength)]; setFocusedIndex(updatedFocusedIndex); - setInput(value); + setEditIndex(updatedFocusedIndex); + setInput(''); const finalInput = composeToString(numbers); props.onChangeText(finalInput); From 1751dfd12ff36d78b65935e284acaba9eebcc4cd Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Fri, 21 Jul 2023 18:05:57 +0200 Subject: [PATCH 004/282] Fix autofocus --- src/components/MagicCodeInput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 9414491bbeae..33fa9c33b837 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,7 +1,8 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} from 'react'; -import {StyleSheet, View, Pressable} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; +import { TapGestureHandler } from 'react-native-gesture-handler'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as ValidationUtils from '../libs/ValidationUtils'; @@ -13,7 +14,6 @@ import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; -import { TapGestureHandler } from 'react-native-gesture-handler'; const propTypes = { /** Information about the network */ @@ -192,14 +192,12 @@ function MagicCodeInput(props) { setEditIndex(lastFocusedIndex.current); } event.preventDefault(); - shouldFocusLast.current = true; }; /** * Callback for the onPress event, updates the indexes * of the currently focused input. * - * @param {Object} event * @param {Number} index */ const onPress = (index) => { @@ -208,6 +206,7 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(index); setEditIndex(index); + lastFocusedIndex.current = index; }; /** @@ -346,6 +345,7 @@ function MagicCodeInput(props) { onKeyPress={onKeyPress} onFocus={onFocus} onBlur={() => { + shouldFocusLast.current = true; lastFocusedIndex.current = focusedIndex; setFocusedIndex(undefined); }} From dc9a37fc4aea5583e33bdb2508cdd14ec74369fd Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Mon, 24 Jul 2023 17:19:32 +0200 Subject: [PATCH 005/282] Remove strange line in magic input --- src/components/MagicCodeInput.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 33fa9c33b837..0cf36bee047e 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -15,6 +15,8 @@ import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; +const TEXT_INPUT_EMPTY_STATE = ''; + const propTypes = { /** Information about the network */ network: networkPropTypes.isRequired, @@ -98,7 +100,7 @@ const getInputPlaceholderSlots = (length) => Array.from(Array(length).keys()); function MagicCodeInput(props) { const inputRefs = useRef(); - const [input, setInput] = useState(''); + const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); const shouldFocusLast = useRef(false); @@ -120,11 +122,11 @@ function MagicCodeInput(props) { focusMagicCodeInput(); }, resetFocus() { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); focusMagicCodeInput(); }, clear() { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(0); setEditIndex(0); inputRefs.current.focus(); @@ -187,7 +189,7 @@ function MagicCodeInput(props) { */ const onFocus = (event) => { if (shouldFocusLast.current) { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(lastFocusedIndex.current); setEditIndex(lastFocusedIndex.current); } @@ -203,7 +205,7 @@ function MagicCodeInput(props) { const onPress = (index) => { shouldFocusLast.current = false; inputRefs.current.focus(); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(index); setEditIndex(index); lastFocusedIndex.current = index; @@ -236,7 +238,7 @@ function MagicCodeInput(props) { setFocusedIndex(updatedFocusedIndex); setEditIndex(updatedFocusedIndex); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); const finalInput = composeToString(numbers); props.onChangeText(finalInput); @@ -257,7 +259,7 @@ function MagicCodeInput(props) { // If the currently focused index already has a value, it will delete // that value but maintain the focus on the same input. if (numbers[focusedIndex] !== CONST.MAGIC_CODE_EMPTY_CHAR) { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, props.maxLength)]; setEditIndex(focusedIndex); props.onChangeText(composeToString(numbers)); @@ -280,7 +282,7 @@ function MagicCodeInput(props) { // Saves the input string so that it can compare to the change text // event that will be triggered, this is a workaround for mobile that // triggers the change text on the event after the key press. - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); props.onChangeText(composeToString(numbers)); @@ -291,13 +293,13 @@ function MagicCodeInput(props) { } if (keyValue === 'ArrowLeft' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.max(0, focusedIndex - 1); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); inputRefs.current.focus(); } else if (keyValue === 'ArrowRight' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.min(focusedIndex + 1, props.maxLength - 1); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); inputRefs.current.focus(); @@ -306,7 +308,7 @@ function MagicCodeInput(props) { if (props.network.isOffline) { return; } - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); props.onFulfill(props.value); } }; @@ -352,6 +354,8 @@ function MagicCodeInput(props) { caretHidden={isMobileSafari} inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} + textInputContainerStyles={[styles.borderNone]} /> From 0a5ba32ca07644d3650b25fa8cd056e3a5b326de Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Tue, 1 Aug 2023 15:44:29 +0200 Subject: [PATCH 006/282] Add visible wrapper for android --- src/components/MagicCodeInput.js | 79 ++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 38c5ff72bf48..2b7379bb77e7 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -2,7 +2,7 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} fro import {StyleSheet, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import { TapGestureHandler } from 'react-native-gesture-handler'; +import {TapGestureHandler} from 'react-native-gesture-handler'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as ValidationUtils from '../libs/ValidationUtils'; @@ -321,46 +321,57 @@ function MagicCodeInput(props) { return ( <> - + { - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))) + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > - { - inputWidth.current = e.nativeEvent.layout.width; - }} - ref={(ref) => (inputRefs.current = ref)} - autoFocus={props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={props.autoComplete} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - onChangeText(value); - }} - onKeyPress={onKeyPress} - onFocus={onFocus} - onBlur={() => { - shouldFocusLast.current = true; - lastFocusedIndex.current = focusedIndex; - setFocusedIndex(undefined); - }} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} - textInputContainerStyles={[styles.borderNone]} - /> + {/* Android does not handle touch on invisible Views so I created wrapper around inivisble View just to handle taps */} + + + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + shouldFocusLast.current = true; + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} + textInputContainerStyles={[styles.borderNone]} + /> + + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - + Date: Wed, 23 Aug 2023 11:31:52 +0200 Subject: [PATCH 007/282] Remove index from focus --- src/components/MagicCodeInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 6c5e89e7e2a8..8e4b27fef6c2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -156,7 +156,7 @@ function MagicCodeInput(props) { } // Focus the last input if an error occurred to allow for corrections - inputRefs.current[props.maxLength - 1].focus(); + inputRefs.current.focus(); }, [props.hasError, props.maxLength]); useEffect(() => { From 43a729626bc2a3a252a83dadcc5c28e881e14fb1 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 14:55:28 -0700 Subject: [PATCH 008/282] add distance request consts --- src/CONST.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.js b/src/CONST.js index c78268aacd8b..53a7f14c75b5 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1309,6 +1309,7 @@ const CONST = { DATE: 'date', DESCRIPTION: 'description', MERCHANT: 'merchant', + DISTANCE: 'distance', }, FOOTER: { EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`, From 28c293e462f7d1b58d1bd5f1cdeb4d86955ccad5 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:03:53 -0700 Subject: [PATCH 009/282] add DISTANCE_REQUEST ONYXKEY --- src/ONYXKEYS.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d4d2ab1f90a6..78430e0fdf33 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -41,6 +41,9 @@ const ONYXKEYS = { // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & MoneyRequestPreview Components) IOU: 'iou', + // Contains loading data for the DistanceRequest components (MoneyRequestEditWaypointPage) + DISTANCE_REQUEST: 'distanceRequest', + /** Keeps track if there is modal currently visible or not */ MODAL: 'modal', From 322504aafb2a51ac8ffa998db3b12f24935371f9 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:04:17 -0700 Subject: [PATCH 010/282] add routes for editdistancerequest and editrequestwaypoint --- src/ROUTES.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ROUTES.js b/src/ROUTES.js index bf1beaecb3c3..df36bf3e5069 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -97,6 +97,7 @@ export default { MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', MONEY_REQUEST_DISTANCE_TAB: ':iouType/new/:reportID?/distance', MONEY_REQUEST_WAYPOINT: ':iouType/new/waypoint/:waypointIndex', + MONEY_REQUEST_EDIT_WAYPOINT: 'r/:threadReportID/edit/distance/waypoint/:waypointIndex', IOU_SEND_ADD_BANK_ACCOUNT: `${IOU_SEND}/add-bank-account`, IOU_SEND_ADD_DEBIT_CARD: `${IOU_SEND}/add-debit-card`, IOU_SEND_ENABLE_PAYMENTS: `${IOU_SEND}/enable-payments`, @@ -111,6 +112,7 @@ export default { getMoneyRequestMerchantRoute: (iouType, reportID = '') => `${iouType}/new/merchant/${reportID}`, getMoneyRequestDistanceTabRoute: (iouType, reportID = '') => `${iouType}/new/${reportID}/distance`, getMoneyRequestWaypointRoute: (iouType, waypointIndex) => `${iouType}/new/waypoint/${waypointIndex}`, + getMoneyRequestEditWaypointRoute: (threadReportID, waypointIndex) => `r/${threadReportID}/edit/distance/waypoint/${waypointIndex}`, SPLIT_BILL_DETAILS: `r/:reportID/split/:reportActionID`, getSplitBillDetailsRoute: (reportID, reportActionID) => `r/${reportID}/split/${reportActionID}`, getNewTaskRoute: (reportID) => `${NEW_TASK}/${reportID}`, From c5c72a5bba9430de145a99ca1ae2e4eb81dc789c Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:04:33 -0700 Subject: [PATCH 011/282] add distance translations --- src/languages/en.js | 1 + src/languages/es.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index 5dcb84c4e487..cdc96834289b 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -158,6 +158,7 @@ export default { category: 'Category', receipt: 'Receipt', replace: 'Replace', + distance: 'Distance', }, anonymousReportFooter: { logoTagline: 'Join the discussion.', diff --git a/src/languages/es.js b/src/languages/es.js index 9cb91261cdd5..35ac0c2cc9d8 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -157,6 +157,7 @@ export default { category: 'Categoría', receipt: 'Recibo', replace: 'Sustituir', + distance: 'Distancia', }, anonymousReportFooter: { logoTagline: 'Únete a la discusión.', From 85ac77daa978300b79c7e4386cc0c07f05327e9e Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:04:50 -0700 Subject: [PATCH 012/282] add editDistanceRequest actions --- src/libs/actions/IOU.js | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 838214bbd98e..3f64c2c7c756 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -530,6 +530,61 @@ function createDistanceRequest(report, payeeEmail, payeeAccountID, participant, ); } +/** + * Edits an existing distance request + * + * @param {String} transactionID + * @param {Object} transactionChanges + * @param {String} [transactionChanges.created] + * @param {Number} [transactionChanges.amount] + * @param {String} [transactionChanges.comment] + * @param {String} [transactionChanges.waypoints] + * + */ +function editDistanceRequest(transactionID, transactionThreadReportID, transactionChanges) { + const pendingFields = _.mapObject(updatedProperties, () => CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + const clearedPendingFields = _.mapObject(updatedProperties, () => null); + const errorFields = _.mapObject(pendingFields, () => ({ + [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.edit.genericError'), + })); + + const transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`]; + const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.parentReportID}`]; + const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); + const updatedTransaction = TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport); + API.write( + 'EditDistanceRequest', + {transactionID, ...ReportUtils.getTransactionDetails(updatedTransaction)}, + { + optimisticData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {...updatedProperties, pendingFields}, + }, + ], + successData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {pendingFields: clearedPendingFields}, + }, + ], + failureData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + pendingFields: clearedPendingFields, + errorFields, + }, + }, + ], + }, + ); +} + /** * Request money from another user * @@ -1767,6 +1822,13 @@ function createEmptyTransaction() { Onyx.merge(ONYXKEYS.IOU, {transactionID}); } +/** + * @param {String} transactionID + */ +function setDistanceRequestTransactionID(transactionID) { + Onyx.set(ONYXKEYS.DISTANCE_REQUEST, {transactionID}); +} + /** * Navigates to the next IOU page based on where the IOU request was started * @@ -1825,6 +1887,8 @@ export { setMoneyRequestMerchant, setMoneyRequestParticipants, setMoneyRequestReceipt, + setDistanceRequestTransactionID, createEmptyTransaction, navigateToNextPage, + editDistanceRequest, }; From 4cac0948a58821e3c9587b178eb82af91f657292 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:05:12 -0700 Subject: [PATCH 013/282] add new pages for editing distance requests --- src/pages/EditRequestDistancePage.js | 51 ++++++++++++++++++ src/pages/iou/MoneyRequestEditWaypointPage.js | 52 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/pages/EditRequestDistancePage.js create mode 100644 src/pages/iou/MoneyRequestEditWaypointPage.js diff --git a/src/pages/EditRequestDistancePage.js b/src/pages/EditRequestDistancePage.js new file mode 100644 index 000000000000..f2587db43a14 --- /dev/null +++ b/src/pages/EditRequestDistancePage.js @@ -0,0 +1,51 @@ +import React, {useEffect} from 'react'; +import PropTypes from 'prop-types'; +import ScreenWrapper from '../components/ScreenWrapper'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import Navigation from '../libs/Navigation/Navigation'; +import useLocalize from '../hooks/useLocalize'; +import DistanceRequest from '../components/DistanceRequest'; +import reportPropTypes from './reportPropTypes'; +import * as IOU from '../libs/actions/IOU'; + +const propTypes = { + /** The transactionID we're currently editing */ + transactionID: PropTypes.number, + + /** The report to with which the distance request is associated */ + report: reportPropTypes, +}; + +function EditRequestDistancePage({iou, report}) { + + useEffect(() => { + IOU.setDistanceRequestTransactionID(iou.transactionID); + }, []) + + const {translate} = useLocalize(); + return ( + + Navigation.goBack()} + /> + { + IOU.editDistanceRequest(iou.transactionID, report.reportID, {waypoints}); + Navigation.dismissModal(); + }} + /> + + ); +} + +EditRequestDistancePage.propTypes = propTypes; +EditRequestDistancePage.displayName = 'EditRequestDistancePage'; + +export default EditRequestDistancePage; diff --git a/src/pages/iou/MoneyRequestEditWaypointPage.js b/src/pages/iou/MoneyRequestEditWaypointPage.js new file mode 100644 index 000000000000..4a212cadd957 --- /dev/null +++ b/src/pages/iou/MoneyRequestEditWaypointPage.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import WaypointEditor from './WaypointEditor'; +import ONYXKEYS from '../../ONYXKEYS'; + +const propTypes = { + /** The transactionID of this request */ + transactionID: PropTypes.string, + + /** Route params */ + route: PropTypes.shape({ + params: PropTypes.shape({ + /** IOU type */ + iouType: PropTypes.string, + + /** Index of the waypoint being edited */ + waypointIndex: PropTypes.string, + }), + }), +}; + +const defaultProps = { + transactionID: '', + route: { + params: { + iouType: '', + waypointIndex: '', + }, + }, +}; + +// This component is responsible for grabbing the transactionID from the IOU key +// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that WaypointEditor can subscribe to the transaction. +function MoneyRequestEditWaypointPage({transactionID, route}) { + console.log(">>>> transactionID", transactionID); + return ( + + ); +} + +MoneyRequestEditWaypointPage.displayName = 'MoneyRequestEditWaypointPage'; +MoneyRequestEditWaypointPage.propTypes = propTypes; +MoneyRequestEditWaypointPage.defaultProps = defaultProps; +export default withOnyx({ + // We must provide a default value for transactionID here, otherwise the component won't mount + // because withOnyx returns null until all the keys are defined + transactionID: {key: ONYXKEYS.DISTANCE_REQUEST, selector: (distanceRequest) => (distanceRequest && distanceRequest.transactionID) || ''}, +})(MoneyRequestEditWaypointPage); From ec140606b87805a5dc28f94e5ca816f3dc0f729c Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:05:44 -0700 Subject: [PATCH 014/282] simplify DistanceRequest to just take a transactionID, modify to handle edits --- src/components/DistanceRequest.js | 47 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index c96342dd8965..55975022866a 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -22,7 +22,6 @@ import useNetwork from '../hooks/useNetwork'; import useLocalize from '../hooks/useLocalize'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; -import * as IOU from '../libs/actions/IOU'; import participantPropTypes from './participantPropTypes'; import reportPropTypes from '../pages/reportPropTypes'; import transactionPropTypes from './transactionPropTypes'; @@ -38,17 +37,8 @@ const MAP_PADDING = 50; const DEFAULT_ZOOM_LEVEL = 10; const propTypes = { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: PropTypes.shape({ - id: PropTypes.string, - amount: PropTypes.number, - currency: PropTypes.string, - participants: PropTypes.arrayOf(participantPropTypes), - transactionID: PropTypes.string, - }), - - /** Type of money request (i.e. IOU) */ - iouType: PropTypes.string, + /** The transactionID of the distance request we're currently looking at */ + transactionID: PropTypes.number, /** The report to with which the distance request is associated */ report: reportPropTypes, @@ -64,6 +54,12 @@ const propTypes = { /** Time when the token will expire in ISO 8601 */ expiration: PropTypes.string, }), + + /** Are we editing an existing distance request, or creating a new one? */ + isEditingRequest: PropTypes.bool, + + /** Called on submit of this page */ + onSubmit: PropTypes.func.isRequired, }; const defaultProps = { @@ -71,15 +67,13 @@ const defaultProps = { id: '', amount: 0, currency: CONST.CURRENCY.USD, - participants: [], }, - iouType: '', - report: {}, transaction: {}, mapboxAccessToken: {}, + isEditingRequest: false, }; -function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) { +function DistanceRequest({transactionID, report, transaction, mapboxAccessToken, isEditingRequest, onSubmit}) { const [shouldShowGradient, setShouldShowGradient] = useState(false); const [scrollContainerHeight, setScrollContainerHeight] = useState(0); const [scrollContentHeight, setScrollContentHeight] = useState(0); @@ -136,12 +130,13 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) }, []); useEffect(() => { - if (!iou.transactionID || !_.isEmpty(waypoints)) { + console.log(">>>>", transactionID, waypoints); + if (!transactionID || !_.isEmpty(waypoints)) { return; } // Create the initial start and stop waypoints - Transaction.createInitialWaypoints(iou.transactionID); - }, [iou.transactionID, waypoints]); + Transaction.createInitialWaypoints(transactionID); + }, [transactionID, waypoints]); const updateGradientVisibility = (event = {}) => { // If a waypoint extends past the bottom of the visible area show the gradient, else hide it. @@ -155,8 +150,8 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) return; } - Transaction.getRoute(iou.transactionID, waypoints); - }, [shouldFetchRoute, iou.transactionID, waypoints]); + Transaction.getRoute(transactionID, waypoints); + }, [shouldFetchRoute, transactionID, waypoints]); useEffect(updateGradientVisibility, [scrollContainerHeight, scrollContentHeight]); @@ -196,7 +191,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) secondaryIcon={waypointIcon} secondaryIconFill={theme.icon} shouldShowRightIcon - onPress={() => Navigation.navigate(ROUTES.getMoneyRequestWaypointRoute('request', index))} + onPress={() => Navigation.navigate(isEditingRequest ? ROUTES.getMoneyRequestEditWaypointRoute(report.reportID, index) : ROUTES.getMoneyRequestWaypointRoute('request', index))} key={key} /> ); @@ -220,7 +215,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken})