From 18ad8ccd8b871b79a13ceae99926f8b019134a49 Mon Sep 17 00:00:00 2001 From: bogoslavskiy Date: Thu, 30 Nov 2023 11:59:48 +0300 Subject: [PATCH] fix(mobile): add hints for import wallet --- .../shared/hooks/useRecoveryPhraseInputs.ts | 20 +- .../setup/pages/SetupRecoveryPhrasePage.tsx | 237 +++++++++++++----- .../src/components/KeyboardAccessoryView.tsx | 14 +- 3 files changed, 209 insertions(+), 62 deletions(-) diff --git a/packages/shared/hooks/useRecoveryPhraseInputs.ts b/packages/shared/hooks/useRecoveryPhraseInputs.ts index 448747242..073fb50cd 100644 --- a/packages/shared/hooks/useRecoveryPhraseInputs.ts +++ b/packages/shared/hooks/useRecoveryPhraseInputs.ts @@ -1,6 +1,8 @@ +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useAnimatedStyle } from 'react-native-reanimated'; +import { bip39 } from '@tonkeeper/core/src/bip39'; import { LayoutChangeEvent } from 'react-native'; import { useCallback, useRef } from 'react'; -import { bip39 } from '@tonkeeper/core/src/bip39'; import { useReanimatedKeyboardHeight, ScreenScrollViewRef, @@ -11,6 +13,8 @@ export function useRecoveryPhraseInputs() { const positions = useRef<{ [key in string]: number }>({}); const refs = useRef<{ [key in string]: InputRef }>({}); const scrollViewRef = useRef(null); + const safeArea = useSafeAreaInsets(); + const focusedIndex = useRef(-1); const deferredScrollToInput = useRef<(() => void) | null>(null); const keyboard = useReanimatedKeyboardHeight({ @@ -84,6 +88,8 @@ export function useRecoveryPhraseInputs() { } else { scrollToInput(index); } + + focusedIndex.current = index; }, [scrollToInput], ); @@ -95,6 +101,8 @@ export function useRecoveryPhraseInputs() { if (value && value.length > 0 && !bip39.map[value]) { input.markAsInvalid(); } + + focusedIndex.current = -1; }, [], ); @@ -124,8 +132,16 @@ export function useRecoveryPhraseInputs() { [], ); + const keyboardSpacerStyle = useAnimatedStyle( + () => ({ + height: keyboard.height.value - safeArea.bottom + 32, + }), + [keyboard.height], + ); + return { - keyboardSpacerStyle: keyboard.spacerStyle, + keyboardSpacerStyle, + currentIndex: focusedIndex, refs: refs.current, scrollViewRef, setRef, diff --git a/packages/shared/screens/setup/pages/SetupRecoveryPhrasePage.tsx b/packages/shared/screens/setup/pages/SetupRecoveryPhrasePage.tsx index bf8bb2b4b..80426ace0 100644 --- a/packages/shared/screens/setup/pages/SetupRecoveryPhrasePage.tsx +++ b/packages/shared/screens/setup/pages/SetupRecoveryPhrasePage.tsx @@ -1,14 +1,27 @@ -import { Screen, Button, Spacer, Steezy, Text, View, Input } from '@tonkeeper/uikit'; import { useRecoveryPhraseInputs } from '../../../hooks/useRecoveryPhraseInputs'; import { InputNumberPrefix } from '../../../components/InputNumberPrefix'; +import { SearchIndexer } from '@tonkeeper/core/src/utils/SearchIndexer'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { memo, useCallback, useEffect, useState } from 'react'; - import { bip39 } from '@tonkeeper/core/src/bip39'; -import Animated from 'react-native-reanimated'; +import Animated, { useAnimatedStyle } from 'react-native-reanimated'; import { Pressable } from 'react-native'; +import { + KeyboardAccessoryView, + Screen, + Button, + Spacer, + Steezy, + Text, + View, + Input, + useReanimatedKeyboardHeight, +} from '@tonkeeper/uikit'; const inputsCount = Array(24).fill(0); +const indexedWords = new SearchIndexer(bip39.list); +// const fullMatch = hints.some((item) => item === options.query); interface SetupPhrasePageProps { onNext: (phrase: string, config?: string) => void; loading: boolean; @@ -17,8 +30,10 @@ interface SetupPhrasePageProps { export const SetupRecoveryPhrasePage = memo((props) => { const { onNext, loading } = props; const inputs = useRecoveryPhraseInputs(); + const safeArea = useSafeAreaInsets(); const [isConfigInputShown, setConfigInputShown] = useState(false); + const [hints, setHints] = useState([]); const [isRestoring, setRestoring] = useState(false); const [config, setConfig] = useState(''); @@ -85,64 +100,149 @@ export const SetupRecoveryPhrasePage = memo((props) => { [handleContinue], ); + const handleChangeText = useCallback( + (inputIndex: number) => (text: string) => { + inputs.onChangeText(inputIndex); + + const hints = indexedWords + .search(text, 6) + .sort((a, b) => { + const preparedQuery = text.toLowerCase().replace(/\s/g, ''); + const aMatch = a.toLowerCase().replace(/\s/g, '').includes(preparedQuery); + const bMatch = b.toLowerCase().replace(/\s/g, '').includes(preparedQuery); + if (aMatch && !bMatch) { + return -1; + } + if (bMatch && !aMatch) { + return 1; + } + + return 0; + }) + .slice(0, 3); + + setHints(hints); + }, + [inputs.onChangeText], + ); + + const handleHintPress = useCallback( + (hint: string) => () => { + console.log(inputs.currentIndex); + inputs.getRef(inputs.currentIndex.current)?.setValue(hint); + inputs.getRef(inputs.currentIndex.current + 1)?.focus(); + setHints([]); + }, + [], + ); + + const keyboard = useReanimatedKeyboardHeight(); + + const keyboardSpacerStyle = useAnimatedStyle( + () => ({ + height: keyboard.height.value - safeArea.bottom + 32 + (hints.length ? 52 : 0), + }), + [keyboard.height, hints.length], + ); + return ( - - - {/* */} - - - Enter recovery phrase + <> + + + {/* */} + + + Enter recovery phrase + + + {/* */} + + + When you created this wallet, you got a 24-word recovery phrase. Enter it to + restore access to your wallet. + + + {isConfigInputShown && ( + + )} + {inputsCount.map((_, index) => ( + } + returnKeyType={index === 23 ? 'done' : 'next'} + onSubmitEditing={handleInputSubmit(index)} + onChangeText={handleChangeText(index)} + onLayout={inputs.setPosition(index)} + onFocus={inputs.onFocus(index)} + onBlur={inputs.onBlur(index)} + ref={inputs.setRef(index)} + disableAutoMarkValid + style={styles.input} + indentBottom + keyboardType="ascii-capable" + textContentType="none" + autoCapitalize="none" + autoComplete="off" + autoCorrect={false} + /> + ))} + + +