1- import React , {
2- memo ,
3- ReactElement ,
4- useState ,
5- useEffect ,
6- useCallback ,
7- } from 'react' ;
8- import { StyleSheet , View , Pressable } from 'react-native' ;
9- import { FadeIn , FadeOut } from 'react-native-reanimated' ;
1+ import React , { ReactElement , memo , useEffect , useState } from 'react' ;
102import { useTranslation } from 'react-i18next' ;
3+ import { Pressable , StyleSheet , View } from 'react-native' ;
4+ import { FadeIn , FadeOut } from 'react-native-reanimated' ;
115
12- import { BodyS , Subtitle } from '../styles/text' ;
13- import { View as ThemedView , AnimatedView } from '../styles/components' ;
6+ import BitkitLogo from '../assets/bitkit-logo.svg' ;
7+ import { PIN_ATTEMPTS } from '../constants/app' ;
8+ import usePIN from '../hooks/pin' ;
9+ import { showBottomSheet } from '../store/utils/ui' ;
10+ import { AnimatedView , View as ThemedView } from '../styles/components' ;
1411import { FaceIdIcon , TouchIdIcon } from '../styles/icons' ;
15- import SafeAreaInset from './SafeAreaInset ' ;
16- import NavigationHeader from './NavigationHeader ' ;
12+ import { BodyS , Subtitle } from '../styles/text ' ;
13+ import rnBiometrics from '../utils/biometrics ' ;
1714import { IsSensorAvailableResult } from './Biometrics' ;
15+ import NavigationHeader from './NavigationHeader' ;
1816import NumberPad from './NumberPad' ;
17+ import SafeAreaInset from './SafeAreaInset' ;
1918import Button from './buttons/Button' ;
20- import useColors from '../hooks/colors' ;
21- import { wipeApp } from '../store/utils/settings' ;
22- import { showBottomSheet } from '../store/utils/ui' ;
23- import { vibrate } from '../utils/helpers' ;
24- import rnBiometrics from '../utils/biometrics' ;
25- import { showToast } from '../utils/notifications' ;
26- import { setKeychainValue , getKeychainValue } from '../utils/keychain' ;
27- import BitkitLogo from '../assets/bitkit-logo.svg' ;
28- import { PIN_ATTEMPTS } from '../constants/app' ;
2919
3020const PinPad = ( {
3121 showLogoOnPIN,
@@ -37,122 +27,22 @@ const PinPad = ({
3727 showLogoOnPIN : boolean ;
3828 allowBiometrics : boolean ;
3929 showBackNavigation ?: boolean ;
40- onSuccess : ( ) => void ;
30+ onSuccess ? : ( ) => void ;
4131 onShowBiotmetrics ?: ( ) => void ;
4232} ) : ReactElement => {
4333 const { t } = useTranslation ( 'security' ) ;
44- const { brand, brand08 } = useColors ( ) ;
45- const [ pin , setPin ] = useState ( '' ) ;
46- const [ isLoading , setIsLoading ] = useState ( true ) ;
47- const [ attemptsRemaining , setAttemptsRemaining ] = useState ( 0 ) ;
4834 const [ biometryData , setBiometricData ] = useState < IsSensorAvailableResult > ( ) ;
49-
50- const handleOnPress = ( key : string ) : void => {
51- vibrate ( ) ;
52- if ( key === 'delete' ) {
53- setPin ( ( p ) => {
54- return p . length === 0 ? '' : p . slice ( 0 , - 1 ) ;
55- } ) ;
56- } else {
57- setPin ( ( p ) => {
58- return p . length === 4 ? p : p + key ;
59- } ) ;
60- }
61- } ;
62-
63- // Reduce the amount of pin attempts remaining.
64- const reducePinAttemptsRemaining = useCallback ( async ( ) : Promise < void > => {
65- const _attemptsRemaining = attemptsRemaining - 1 ;
66- await setKeychainValue ( {
67- key : 'pinAttemptsRemaining' ,
68- value : `${ _attemptsRemaining } ` ,
69- } ) ;
70- setAttemptsRemaining ( _attemptsRemaining ) ;
71- } , [ attemptsRemaining ] ) ;
72-
73- // init view
74- useEffect ( ( ) => {
75- ( async ( ) : Promise < void > => {
76- const attemptsRemainingResponse = await getKeychainValue ( {
77- key : 'pinAttemptsRemaining' ,
78- } ) ;
79-
80- if (
81- ! attemptsRemainingResponse . error &&
82- Number ( attemptsRemainingResponse . data ) !== Number ( attemptsRemaining )
83- ) {
84- let numAttempts =
85- attemptsRemainingResponse . data !== undefined
86- ? Number ( attemptsRemainingResponse . data )
87- : 5 ;
88- setAttemptsRemaining ( numAttempts ) ;
89- }
90- } ) ( ) ;
91- } , [ attemptsRemaining ] ) ;
35+ const { attemptsRemaining, Dots, handleNumberPress, isLastAttempt, loading } =
36+ usePIN ( onSuccess ) ;
9237
9338 // on mount
9439 useEffect ( ( ) => {
9540 ( async ( ) : Promise < void > => {
96- setIsLoading ( true ) ;
97- // wait for initial keychain read
98- await getKeychainValue ( { key : 'pinAttemptsRemaining' } ) ;
99- // get available biometrics
10041 const data = await rnBiometrics . isSensorAvailable ( ) ;
10142 setBiometricData ( data ) ;
102- setIsLoading ( false ) ;
10343 } ) ( ) ;
10444 } , [ ] ) ;
10545
106- // submit pin
107- useEffect ( ( ) => {
108- const timer = setTimeout ( async ( ) => {
109- if ( pin . length !== 4 ) {
110- return ;
111- }
112-
113- const realPIN = await getKeychainValue ( { key : 'pin' } ) ;
114-
115- // error getting pin
116- if ( realPIN . error ) {
117- await reducePinAttemptsRemaining ( ) ;
118- vibrate ( ) ;
119- setPin ( '' ) ;
120- return ;
121- }
122-
123- // incorrect pin
124- if ( pin !== realPIN ?. data ) {
125- if ( attemptsRemaining <= 1 ) {
126- vibrate ( { type : 'default' } ) ;
127- await wipeApp ( ) ;
128- showToast ( {
129- type : 'warning' ,
130- title : t ( 'wiped_title' ) ,
131- description : t ( 'wiped_message' ) ,
132- } ) ;
133- } else {
134- await reducePinAttemptsRemaining ( ) ;
135- }
136-
137- vibrate ( ) ;
138- setPin ( '' ) ;
139- return ;
140- }
141-
142- // correct pin
143- await setKeychainValue ( {
144- key : 'pinAttemptsRemaining' ,
145- value : PIN_ATTEMPTS ,
146- } ) ;
147- setPin ( '' ) ;
148- onSuccess ?.( ) ;
149- } , 500 ) ;
150-
151- return ( ) : void => clearTimeout ( timer ) ;
152- } , [ pin , attemptsRemaining , onSuccess , reducePinAttemptsRemaining , t ] ) ;
153-
154- const isLastAttempt = attemptsRemaining === 1 ;
155-
15646 const biometricsName =
15747 biometryData ?. biometryType === 'TouchID'
15848 ? t ( 'bio_touch_id' )
@@ -172,7 +62,7 @@ const PinPad = ({
17262 </ View >
17363
17464 < View style = { styles . content } >
175- { ! isLoading && (
65+ { ! loading && biometryData !== undefined && (
17666 < AnimatedView
17767 style = { styles . contentInner }
17868 color = "transparent"
@@ -210,40 +100,26 @@ const PinPad = ({
210100 < Button
211101 style = { styles . biometrics }
212102 text = { t ( 'pin_use_biometrics' , { biometricsName } ) }
103+ onPress = { onShowBiotmetrics }
213104 icon = {
214105 biometryData ?. biometryType === 'FaceID' ? (
215106 < FaceIdIcon height = { 16 } width = { 16 } color = "brand" />
216107 ) : (
217108 < TouchIdIcon height = { 16 } width = { 16 } color = "brand" />
218109 )
219110 }
220- onPress = { onShowBiotmetrics }
221111 />
222112 ) }
223113 </ View >
224114
225115 < View style = { styles . dots } >
226- { Array ( 4 )
227- . fill ( null )
228- . map ( ( _ , i ) => (
229- < View
230- key = { i }
231- style = { [
232- styles . dot ,
233- {
234- borderColor : brand ,
235- backgroundColor :
236- pin [ i ] === undefined ? brand08 : brand ,
237- } ,
238- ] }
239- />
240- ) ) }
116+ < Dots />
241117 </ View >
242118
243119 < NumberPad
244120 style = { styles . numberpad }
245121 type = "simple"
246- onPress = { handleOnPress }
122+ onPress = { handleNumberPress }
247123 />
248124 </ AnimatedView >
249125 ) }
@@ -302,13 +178,6 @@ const styles = StyleSheet.create({
302178 marginBottom : 16 ,
303179 } ,
304180 dots : {
305- flexDirection : 'row' ,
306- justifyContent : 'center' ,
307- marginBottom : 48 ,
308- } ,
309- dot : {
310- width : 20 ,
311- height : 20 ,
312181 borderRadius : 10 ,
313182 marginHorizontal : 12 ,
314183 borderWidth : 1 ,
0 commit comments