@@ -14,7 +14,6 @@ import {
1414 Platform ,
1515 FlatList ,
1616 TouchableOpacity ,
17- Keyboard ,
1817} from 'react-native' ;
1918import { connect } from 'react-redux' ;
2019import StorageWrapper from '../../../store/storage-wrapper' ;
@@ -96,6 +95,7 @@ const PASSCODE_NOT_SET_ERROR = 'Error: Passcode not set.';
9695const IOS_REJECTED_BIOMETRICS_ERROR =
9796 'Error: The user name or passphrase you entered is not correct.' ;
9897
98+ const SPACE_CHAR = ' ' ;
9999/**
100100 * View where users can set restore their account
101101 * using a secret recovery phrase (SRP)
@@ -131,7 +131,9 @@ const ImportFromSecretRecoveryPhrase = ({
131131 const [ hideSeedPhraseInput , setHideSeedPhraseInput ] = useState ( true ) ;
132132 const [ seedPhrase , setSeedPhrase ] = useState ( [ ] ) ;
133133 const [ seedPhraseInputFocusedIndex , setSeedPhraseInputFocusedIndex ] =
134- useState ( - 1 ) ;
134+ useState ( 0 ) ;
135+ const [ nextSeedPhraseInputFocusedIndex , setNextSeedPhraseInputFocusedIndex ] =
136+ useState ( 0 ) ;
135137 const [ showAllSeedPhrase , setShowAllSeedPhrase ] = useState ( false ) ;
136138 const [ currentStep , setCurrentStep ] = useState ( 0 ) ;
137139 const [ learnMore , setLearnMore ] = useState ( false ) ;
@@ -160,6 +162,66 @@ const ImportFromSecretRecoveryPhrase = ({
160162 trackOnboarding ( eventBuilder . build ( ) ) ;
161163 } ;
162164
165+ const checkValidSeedWord = useCallback ( ( text ) => wordlist . includes ( text ) , [ ] ) ;
166+
167+ const [ isAnyWordError , setIsAnyNewWordError ] = useState ( false ) ;
168+
169+ const handleClear = useCallback ( ( ) => {
170+ setSeedPhrase ( [ ] ) ;
171+ setShowAllSeedPhrase ( false ) ;
172+ setError ( '' ) ;
173+ } , [ ] ) ;
174+
175+ const handleSeedPhraseChange = useCallback (
176+ ( text , index ) => {
177+ if ( error ) setError ( '' ) ;
178+
179+ if ( text . includes ( SPACE_CHAR ) ) {
180+ const isEndWithSpace = text . at ( - 1 ) === SPACE_CHAR ;
181+ // handle use pasting multiple words / whole seed phrase separated by spaces
182+ const splitArray = text . trim ( ) . split ( ' ' ) ;
183+
184+ if ( splitArray . length > 1 ) {
185+ const isAllValid = splitArray . reduce (
186+ ( acc , item ) => acc && checkValidSeedWord ( item ) ,
187+ true ,
188+ ) ;
189+
190+ if ( ! isAllValid ) {
191+ setIsAnyNewWordError ( true ) ;
192+ }
193+ }
194+
195+ let totalLength = seedPhrase . length + splitArray . length ;
196+ setSeedPhrase ( ( prev ) => {
197+ const endSlices = prev . slice ( index + 1 ) ;
198+ if ( endSlices . length === 0 && isEndWithSpace ) {
199+ endSlices . push ( '' ) ;
200+ totalLength ++ ;
201+ }
202+ return [ ...prev . slice ( 0 , index ) , ...splitArray , ...endSlices ] ;
203+ // input the array into the correct index
204+ } ) ;
205+
206+ setNextSeedPhraseInputFocusedIndex ( totalLength - 1 ) ;
207+ } else {
208+ setSeedPhrase ( ( prev ) => {
209+ // update the word at the correct index
210+ const newSeedPhrase = [ ...prev ] ;
211+ newSeedPhrase [ index ] = text . trim ( ) ;
212+ return newSeedPhrase ;
213+ } ) ;
214+ }
215+ } ,
216+ [
217+ error ,
218+ seedPhrase ,
219+ setSeedPhrase ,
220+ setNextSeedPhraseInputFocusedIndex ,
221+ checkValidSeedWord ,
222+ ] ,
223+ ) ;
224+
163225 const onQrCodePress = useCallback ( ( ) => {
164226 let shouldHideSRP = true ;
165227 if ( ! hideSeedPhraseInput ) {
@@ -172,7 +234,8 @@ const ImportFromSecretRecoveryPhrase = ({
172234 disableTabber : true ,
173235 onScanSuccess : ( { seed = undefined } ) => {
174236 if ( seed ) {
175- setSeedPhrase ( seed ) ;
237+ handleClear ( ) ;
238+ handleSeedPhraseChange ( seed , 0 ) ;
176239 } else {
177240 Alert . alert (
178241 strings ( 'import_from_seed.invalid_qr_code_title' ) ,
@@ -185,7 +248,7 @@ const ImportFromSecretRecoveryPhrase = ({
185248 setHideSeedPhraseInput ( shouldHideSRP ) ;
186249 } ,
187250 } ) ;
188- } , [ hideSeedPhraseInput , navigation ] ) ;
251+ } , [ hideSeedPhraseInput , navigation , handleClear , handleSeedPhraseChange ] ) ;
189252
190253 const onBackPress = ( ) => {
191254 if ( currentStep === 0 ) {
@@ -359,88 +422,32 @@ const ImportFromSecretRecoveryPhrase = ({
359422
360423 const passwordStrengthWord = getPasswordStrengthWord ( passwordStrength ) ;
361424
362- const handleSeedPhraseChange = ( text , index ) => {
363- setError ( '' ) ;
364- if ( text . includes ( ' ' ) ) {
365- setSeedPhrase ( ( prev ) => {
366- // handle use pasting multiple words / whole seed phrase separated by spaces
367- const splitArray = text . trim ( ) . split ( / \s + / ) ; // split by any spaces
368- return [
369- ...prev . slice ( 0 , index ) ,
370- ...splitArray ,
371- ...prev . slice ( index + 1 ) ,
372- ] ; // input the array into the correct index
373- } ) ;
374- } else {
375- setSeedPhrase ( ( prev ) => {
376- // update the word at the correct index
377- const newSeedPhrase = [ ...prev ] ;
378- newSeedPhrase [ index ] = text . trim ( ) ;
379- return newSeedPhrase ;
380- } ) ;
381- }
382- } ;
383-
384425 const handleKeyPress = ( e , index , enterPressed = false ) => {
385- setError ( '' ) ;
386426 const { key } = e . nativeEvent ;
387427 if ( key === 'Backspace' ) {
388- if ( index === 0 && seedPhrase . length === 1 ) {
389- setSeedPhrase ( [ '' ] ) ;
390- } else if ( seedPhrase [ index ] === '' ) {
428+ if ( seedPhrase [ index ] === '' ) {
391429 const newData = seedPhrase . filter ( ( _ , idx ) => idx !== index ) ;
392430 setSeedPhrase ( newData ) ;
393- seedPhraseInputRefs . current [ index - 1 ] ?. focus ( ) ;
394- } else {
395- const newData = [ ...seedPhrase ] ;
396- newData [ index ] = '' ;
397- setSeedPhrase ( newData ) ;
398- seedPhraseInputRefs . current [ index ] ?. focus ( ) ;
431+ setNextSeedPhraseInputFocusedIndex ( newData . length - 1 ) ;
399432 }
400433 return ;
401434 }
402- if (
403- ( key === ' ' || key === 'Enter' || key === 'return' || enterPressed ) &&
404- index === seedPhrase . length - 1 &&
405- seedPhrase [ index ] !== ''
406- ) {
407- setSeedPhrase ( [ ...seedPhrase , '' ] ) ;
408- seedPhraseInputRefs . current [ index + 1 ] ?. focus ( ) ;
409- return ;
410- }
411- if (
412- ( key === ' ' || key === 'Enter' || key === 'return' || enterPressed ) &&
413- seedPhrase [ index ] !== ''
414- ) {
415- const firstList = seedPhrase . slice ( 0 , index + 1 ) ;
416- const secondList = seedPhrase . slice ( index + 1 ) ;
417- setSeedPhrase ( [ ...firstList , ' ' , ...secondList ] ) ;
418- seedPhraseInputRefs . current [ index + 1 ] ?. focus ( ) ;
419- return ;
420- }
421435 } ;
422436
423- const handlePaste = async ( ) => {
424- setError ( '' ) ;
425- const text = await Clipboard . getString ( ) ; // Get copied text
426- if ( text . trim ( ) !== '' ) {
427- const pastedData = text . split ( ' ' ) ; // Split by spaces
428- setSeedPhrase ( [ ...pastedData ] . filter ( ( item ) => item !== '' ) ) ;
429- Keyboard . dismiss ( ) ;
430- seedPhraseInputRefs . current [ seedPhrase . length ] ?. focus ( ) ;
431- }
432- } ;
437+ const handlePaste = useCallback (
438+ async ( focusedIndex ) => {
439+ const text = await Clipboard . getString ( ) ; // Get copied text
440+ if ( text . trim ( ) !== '' ) {
441+ handleSeedPhraseChange ( text , focusedIndex ) ;
442+ }
443+ } ,
444+ [ handleSeedPhraseChange ] ,
445+ ) ;
433446
434447 const toggleShowAllSeedPhrase = ( ) => {
435448 setShowAllSeedPhrase ( ( prev ) => ! prev ) ;
436449 } ;
437450
438- const handleClear = ( ) => {
439- setSeedPhrase ( [ ] ) ;
440- setShowAllSeedPhrase ( false ) ;
441- setError ( '' ) ;
442- } ;
443-
444451 const validateSeedPhrase = ( ) => {
445452 const phrase = seedPhrase . filter ( ( item ) => item !== '' ) . join ( ' ' ) ;
446453 const seedPhraseLength = seedPhrase . length ;
@@ -610,14 +617,6 @@ const ImportFromSecretRecoveryPhrase = ({
610617 const isError =
611618 password !== '' && confirmPassword !== '' && password !== confirmPassword ;
612619
613- const isValidSeed = ( text ) => {
614- const isValid = wordlist . includes ( text ) ;
615- if ( ! isValid ) {
616- setError ( strings ( 'import_from_seed.spellcheck_error' ) ) ;
617- }
618- return isValid ;
619- } ;
620-
621620 const showWhatIsSeedPhrase = ( ) => {
622621 navigation . navigate ( Routes . MODAL . ROOT_MODAL_FLOW , {
623622 screen : Routes . SHEET . SEEDPHRASE_MODAL ,
@@ -634,6 +633,57 @@ const ImportFromSecretRecoveryPhrase = ({
634633 } ) ;
635634 } ;
636635
636+ const getSecureWord = useCallback (
637+ ( word , index , focusedIndex ) => {
638+ const isFocusedWord =
639+ focusedIndex === index &&
640+ focusedIndex !== nextSeedPhraseInputFocusedIndex ;
641+ const isValidSeedWord = checkValidSeedWord ( word ) ;
642+
643+ return word &&
644+ ( showAllSeedPhrase ? false : seedPhraseInputFocusedIndex !== index ) &&
645+ isValidSeedWord
646+ ? '***'
647+ : word ;
648+ } ,
649+ [
650+ seedPhraseInputFocusedIndex ,
651+ nextSeedPhraseInputFocusedIndex ,
652+ showAllSeedPhrase ,
653+ checkValidSeedWord ,
654+ ] ,
655+ ) ;
656+
657+ useEffect ( ( ) => {
658+ seedPhraseInputRefs . current [ nextSeedPhraseInputFocusedIndex ] ?. focus ( ) ;
659+ } , [ nextSeedPhraseInputFocusedIndex ] ) ;
660+
661+ const handleOnFocus = useCallback (
662+ ( index ) => {
663+ if ( seedPhraseInputFocusedIndex !== index ) {
664+ setError ( '' ) ;
665+ const focusOutWord = seedPhrase [ seedPhraseInputFocusedIndex ] ;
666+
667+ if (
668+ isAnyWordError ||
669+ ( focusOutWord && ! checkValidSeedWord ( focusOutWord ) )
670+ ) {
671+ setIsAnyNewWordError ( false ) ;
672+ setError ( strings ( 'import_from_seed.spellcheck_error' ) ) ;
673+ }
674+ }
675+ setSeedPhraseInputFocusedIndex ( index ) ;
676+ } ,
677+ [
678+ seedPhrase ,
679+ seedPhraseInputFocusedIndex ,
680+ setError ,
681+ isAnyWordError ,
682+ checkValidSeedWord ,
683+ setSeedPhraseInputFocusedIndex ,
684+ ] ,
685+ ) ;
686+
637687 return (
638688 < SafeAreaView style = { styles . root } >
639689 < KeyboardAwareScrollView
@@ -679,6 +729,9 @@ const ImportFromSecretRecoveryPhrase = ({
679729 < View style = { styles . seedPhraseInnerContainer } >
680730 { seedPhrase . length <= 1 ? (
681731 < TextInput
732+ ref = { ( ref ) => {
733+ seedPhraseInputRefs . current [ 0 ] = ref ;
734+ } }
682735 textAlignVertical = "top"
683736 label = { strings ( 'import_from_seed.srp' ) }
684737 placeholder = { strings (
@@ -719,6 +772,9 @@ const ImportFromSecretRecoveryPhrase = ({
719772 ] }
720773 >
721774 < TextField
775+ ref = { ( ref ) => {
776+ seedPhraseInputRefs . current [ index ] = ref ;
777+ } }
722778 startAccessory = {
723779 < Text
724780 variant = { TextVariant . BodyMD }
@@ -728,25 +784,20 @@ const ImportFromSecretRecoveryPhrase = ({
728784 { index + 1 } .
729785 </ Text >
730786 }
731- value = {
732- item &&
733- ( showAllSeedPhrase
734- ? false
735- : seedPhraseInputFocusedIndex !==
736- index ) &&
737- isValidSeed ( item )
738- ? '***'
739- : item
740- }
787+ value = { getSecureWord (
788+ item ,
789+ index ,
790+ seedPhraseInputFocusedIndex ,
791+ ) }
741792 secureTextEntry = {
742- isValidSeed ( item ) &&
793+ checkValidSeedWord ( item ) &&
743794 ( showAllSeedPhrase
744795 ? false
745796 : seedPhraseInputFocusedIndex !== index )
746797 }
747- onFocus = { ( ) =>
748- setSeedPhraseInputFocusedIndex ( index )
749- }
798+ onFocus = { ( ) => {
799+ handleOnFocus ( index ) ;
800+ } }
750801 onChangeText = { ( text ) =>
751802 handleSeedPhraseChange ( text , index )
752803 }
@@ -766,7 +817,7 @@ const ImportFromSecretRecoveryPhrase = ({
766817 textAlignVertical = "center"
767818 showSoftInputOnFocus
768819 blurOnSubmit = { false }
769- isError = { ! isValidSeed ( item ) }
820+ isError = { ! checkValidSeedWord ( item ) }
770821 autoCapitalize = "none"
771822 numberOfLines = { 1 }
772823 />
@@ -800,7 +851,7 @@ const ImportFromSecretRecoveryPhrase = ({
800851 if ( seedPhrase . length > 1 ) {
801852 handleClear ( ) ;
802853 } else {
803- handlePaste ( ) ;
854+ handlePaste ( seedPhraseInputFocusedIndex ) ;
804855 }
805856 } }
806857 width = { ButtonWidthTypes . Full }
0 commit comments