From 8496d9904e37d988c484da17e1c54c7107a4cd34 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 13 Jul 2023 16:20:23 +0200 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 05/17] 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 06/17] 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 07/17] 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 6ece16c37fc21980370be83d58cbaf52ddab34ef Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 30 Aug 2023 14:27:39 +0200 Subject: [PATCH 08/17] Reset position after clear --- src/components/MagicCodeInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b5b4850cf0fa..0879ea8badc4 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -123,6 +123,7 @@ function MagicCodeInput(props) { clear() { setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(0); + lastFocusedIndex.current = 0; setEditIndex(0); inputRefs.current.focus(); props.onChangeText(''); From 43dc30f7f98b608dd98f3c585e1709c81bdfa7ba Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 30 Aug 2023 17:48:48 +0200 Subject: [PATCH 09/17] Improve usability of input on mobile web browser --- src/components/MagicCodeInput.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 0879ea8badc4..d2a2a7e8ed9c 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -13,6 +13,7 @@ import FormHelpMessage from './FormHelpMessage'; import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useNetwork from '../hooks/useNetwork'; +import * as Browser from '../libs/Browser'; const TEXT_INPUT_EMPTY_STATE = ''; @@ -187,7 +188,9 @@ function MagicCodeInput(props) { */ const onPress = (index) => { shouldFocusLast.current = false; - inputRefs.current.focus(); + if (!Browser.isMobileChrome()) { + inputRefs.current.focus(); + } setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(index); setEditIndex(index); @@ -301,7 +304,16 @@ function MagicCodeInput(props) { { + if (!Browser.isMobileSafari()) { + return; + } + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); + }} onBegan={(e) => { + if (Browser.isMobileSafari()) { + return; + } onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > From 1e72e48d2c488db426aa1d623b4a2e6bd77f968a Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 31 Aug 2023 10:53:37 +0200 Subject: [PATCH 10/17] Make mobile browser code more readable --- src/components/MagicCodeInput.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index d2a2a7e8ed9c..9ec950bee75b 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -188,7 +188,7 @@ function MagicCodeInput(props) { */ const onPress = (index) => { shouldFocusLast.current = false; - if (!Browser.isMobileChrome()) { + if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { inputRefs.current.focus(); } setInput(TEXT_INPUT_EMPTY_STATE); @@ -304,16 +304,7 @@ function MagicCodeInput(props) { { - if (!Browser.isMobileSafari()) { - return; - } - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); - }} onBegan={(e) => { - if (Browser.isMobileSafari()) { - return; - } onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > From c21cb051214e36cc1a1427b847d2899713a7997e Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Mon, 4 Sep 2023 16:21:15 +0200 Subject: [PATCH 11/17] Add focus comment --- src/components/MagicCodeInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 9ec950bee75b..2cbe8ed2b5f2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -188,6 +188,8 @@ function MagicCodeInput(props) { */ const onPress = (index) => { shouldFocusLast.current = false; + // TapGestureHandler works differently on mobile web and native app + // On web gesture handelr doesn't block interactions with textInput below so there is no need to run `focus()` manually if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { inputRefs.current.focus(); } From 4cfd1e03665930e6a774f6d562a6310d1639a8f6 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Tue, 5 Sep 2023 15:10:09 +0200 Subject: [PATCH 12/17] Remove not necessary Views --- src/components/MagicCodeInput.js | 86 +++++++++++++++----------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 050750b6f59f..32942915fb0a 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -304,52 +304,48 @@ function MagicCodeInput(props) { return ( <> - - { - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); - }} + { + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); + }} + > + {/* Android does not handle touch on invisible Views so I created wrapper around inivisble TextInput just to handle taps */} + - {/* 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} - 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); - }} - selectionColor="transparent" - inputStyle={[styles.inputTransparent]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - style={[styles.inputTransparent]} - textInputContainerStyles={[styles.borderNone]} - /> - - - - + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus} + 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); + }} + selectionColor="transparent" + inputStyle={[styles.inputTransparent]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[styles.inputTransparent]} + textInputContainerStyles={[styles.borderNone]} + /> + + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( Date: Tue, 5 Sep 2023 18:46:50 +0200 Subject: [PATCH 13/17] Update src/components/MagicCodeInput.js Co-authored-by: Santhosh Sellavel <85645967+Santhosh-Sellavel@users.noreply.github.com> --- 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 32942915fb0a..caf21b7dd08f 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -189,7 +189,7 @@ function MagicCodeInput(props) { const onPress = (index) => { shouldFocusLast.current = false; // TapGestureHandler works differently on mobile web and native app - // On web gesture handelr doesn't block interactions with textInput below so there is no need to run `focus()` manually + // On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { inputRefs.current.focus(); } From 5927668c6d907846fb72d1da5963656c2c9502e2 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Tue, 19 Sep 2023 15:11:41 +0200 Subject: [PATCH 14/17] Clear input right away when asking for new one --- .../Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index bcea33d9c366..300bd23cc2e5 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -111,6 +111,7 @@ function BaseValidateCodeForm(props) { const resendValidateCode = () => { User.requestContactMethodValidateCode(props.contactMethod); setValidateCode(''); + inputValidateCodeRef.current.clear(); inputValidateCodeRef.current.focus(); }; From 61725c09eff0b5154cf2c217161846ca7d6b7c94 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 21 Sep 2023 14:39:55 +0200 Subject: [PATCH 15/17] Reset index on focus --- src/components/MagicCodeInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index caf21b7dd08f..b8501f4d227c 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -110,6 +110,8 @@ function MagicCodeInput(props) { const focusMagicCodeInput = () => { setFocusedIndex(0); + lastFocusedIndex.current = 0; + setEditIndex(0); inputRefs.current.focus(); }; From c021644089574c286452454afa7021acc1720049 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Mon, 25 Sep 2023 19:25:47 +0200 Subject: [PATCH 16/17] Remove refocus on error code --- src/components/MagicCodeInput.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b8501f4d227c..433dbe2433c3 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -149,15 +149,6 @@ function MagicCodeInput(props) { useNetwork({onReconnect: validateAndSubmit}); - useEffect(() => { - if (!props.hasError) { - return; - } - - // Focus the last input if an error occurred to allow for corrections - inputRefs.current.focus(); - }, [props.hasError, props.maxLength]); - useEffect(() => { validateAndSubmit(); From 6aa803f3127f5eaa70b6894d295d9b86c1dd2be7 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz <42337257+wojtus7@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:40:16 +0200 Subject: [PATCH 17/17] Update src/components/MagicCodeInput.js Co-authored-by: Daniel Gale-Rosen <5487802+dangrous@users.noreply.github.com> --- 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 433dbe2433c3..1db1acddc5d7 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -302,7 +302,7 @@ function MagicCodeInput(props) { onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); }} > - {/* Android does not handle touch on invisible Views so I created wrapper around inivisble TextInput just to handle taps */} + {/* Android does not handle touch on invisible Views so I created a wrapper around invisible TextInput just to handle taps */}