Skip to content

Commit

Permalink
Merge pull request #54253 from margelo/fix/otp-inserting-now-working
Browse files Browse the repository at this point in the history
fix: auto insert OTP not working
  • Loading branch information
mountiny authored Dec 28, 2024
2 parents 9412d21 + eedaac8 commit b7b5dda
Showing 1 changed file with 38 additions and 22 deletions.
60 changes: 38 additions & 22 deletions src/components/MagicCodeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,44 +114,52 @@ function MagicCodeInput(
) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const inputRefs = useRef<BaseTextInputRef | null>();
const inputRef = useRef<BaseTextInputRef | null>();
const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE);
const [focusedIndex, setFocusedIndex] = useState<number | undefined>(0);
const [editIndex, setEditIndex] = useState(0);
const editIndex = useRef(0);
const [wasSubmitted, setWasSubmitted] = useState(false);
const shouldFocusLast = useRef(false);
const inputWidth = useRef(0);
const lastFocusedIndex = useRef(0);
const lastValue = useRef<string | number>(TEXT_INPUT_EMPTY_STATE);
const valueRef = useRef(value);

useEffect(() => {
lastValue.current = input.length;
}, [input]);

useEffect(() => {
// Note: there are circumstances where the value state isn't updated yet
// when e.g. onChangeText gets called the next time. In those cases its safer to access the value from a ref
// to not have outdated values.
valueRef.current = value;
}, [value]);

const blurMagicCodeInput = () => {
inputRefs.current?.blur();
inputRef.current?.blur();
setFocusedIndex(undefined);
};

const focusMagicCodeInput = () => {
setFocusedIndex(0);
lastFocusedIndex.current = 0;
setEditIndex(0);
inputRefs.current?.focus();
editIndex.current = 0;
inputRef.current?.focus();
};

const setInputAndIndex = (index: number) => {
setInput(TEXT_INPUT_EMPTY_STATE);
setFocusedIndex(index);
setEditIndex(index);
editIndex.current = index;
};

useImperativeHandle(ref, () => ({
focus() {
focusMagicCodeInput();
},
focusLastSelected() {
inputRefs.current?.focus();
inputRef.current?.focus();
},
resetFocus() {
setInput(TEXT_INPUT_EMPTY_STATE);
Expand All @@ -160,7 +168,7 @@ function MagicCodeInput(
clear() {
lastFocusedIndex.current = 0;
setInputAndIndex(0);
inputRefs.current?.focus();
inputRef.current?.focus();
onChangeTextProp('');
},
blur() {
Expand Down Expand Up @@ -219,7 +227,7 @@ function MagicCodeInput(
// TapGestureHandler works differently on mobile web and native app
// 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();
inputRef.current?.focus();
}
setInputAndIndex(index);
lastFocusedIndex.current = index;
Expand All @@ -231,6 +239,12 @@ function MagicCodeInput(
* the focused input on the next empty one, if exists.
* It handles both fast typing and only one digit at a time
* in a specific position.
*
* Note: this works under the assumption that the backing text input will always have a cleared text,
* and entering text will exactly call onChangeText with one new character/digit.
* When the OS is inserting one time passwords for example it will call this method successively with one more digit each time.
* Thus, this method relies on an internal value ref to make sure to always use the latest value (as state updates are async, and
* might happen later than the next call to onChangeText).
*/
const onChangeText = (textValue?: string) => {
if (!textValue?.length || !ValidationUtils.isNumeric(textValue)) {
Expand All @@ -249,16 +263,17 @@ function MagicCodeInput(
const numbersArr = addedValue
.trim()
.split('')
.slice(0, maxLength - editIndex);
const updatedFocusedIndex = Math.min(editIndex + (numbersArr.length - 1) + 1, maxLength - 1);
.slice(0, maxLength - editIndex.current);
const updatedFocusedIndex = Math.min(editIndex.current + (numbersArr.length - 1) + 1, maxLength - 1);

let numbers = decomposeString(value, maxLength);
numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, maxLength)];
let numbers = decomposeString(valueRef.current, maxLength);
numbers = [...numbers.slice(0, editIndex.current), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex.current, maxLength)];

setInputAndIndex(updatedFocusedIndex);

const finalInput = composeToString(numbers);
onChangeTextProp(finalInput);
valueRef.current = finalInput;
};

/**
Expand All @@ -275,12 +290,13 @@ function MagicCodeInput(
// If keyboard is disabled and no input is focused we need to remove
// the last entered digit and focus on the correct input
if (isDisableKeyboard && focusedIndex === undefined) {
const indexBeforeLastEditIndex = editIndex === 0 ? editIndex : editIndex - 1;
const curEditIndex = editIndex.current;
const indexBeforeLastEditIndex = curEditIndex === 0 ? curEditIndex : curEditIndex - 1;

const indexToFocus = numbers.at(editIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex;
const indexToFocus = numbers.at(curEditIndex) === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : curEditIndex;
if (indexToFocus !== undefined) {
lastFocusedIndex.current = indexToFocus;
inputRefs.current?.focus();
inputRef.current?.focus();
}
onChangeTextProp(value.substring(0, indexToFocus));

Expand All @@ -292,7 +308,7 @@ function MagicCodeInput(
if (focusedIndex !== undefined && numbers?.at(focusedIndex) !== CONST.MAGIC_CODE_EMPTY_CHAR) {
setInput(TEXT_INPUT_EMPTY_STATE);
numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, maxLength)];
setEditIndex(focusedIndex);
editIndex.current = focusedIndex;
onChangeTextProp(composeToString(numbers));
return;
}
Expand All @@ -318,17 +334,17 @@ function MagicCodeInput(

if (newFocusedIndex !== undefined) {
lastFocusedIndex.current = newFocusedIndex;
inputRefs.current?.focus();
inputRef.current?.focus();
}
}
if (keyValue === 'ArrowLeft' && focusedIndex !== undefined) {
const newFocusedIndex = Math.max(0, focusedIndex - 1);
setInputAndIndex(newFocusedIndex);
inputRefs.current?.focus();
inputRef.current?.focus();
} else if (keyValue === 'ArrowRight' && focusedIndex !== undefined) {
const newFocusedIndex = Math.min(focusedIndex + 1, maxLength - 1);
setInputAndIndex(newFocusedIndex);
inputRefs.current?.focus();
inputRef.current?.focus();
} else if (keyValue === 'Enter') {
// We should prevent users from submitting when it's offline.
if (isOffline) {
Expand All @@ -340,7 +356,7 @@ function MagicCodeInput(
const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1;
if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) {
setInputAndIndex(newFocusedIndex);
inputRefs.current?.focus();
inputRef.current?.focus();
if (event?.preventDefault) {
event.preventDefault();
}
Expand Down Expand Up @@ -383,7 +399,7 @@ function MagicCodeInput(
onLayout={(e) => {
inputWidth.current = e.nativeEvent.layout.width;
}}
ref={(inputRef) => (inputRefs.current = inputRef)}
ref={(newRef) => (inputRef.current = newRef)}
autoFocus={autoFocus}
inputMode="numeric"
textContentType="oneTimeCode"
Expand Down

0 comments on commit b7b5dda

Please sign in to comment.