import React, { useCallback, useContext, useEffect, useReducer, useRef } from 'react'; import { View, Image, TouchableOpacity, ActivityIndicator, useColorScheme, NativeModules, StyleSheet } from 'react-native'; import { Icon } from 'react-native-elements'; import Biometric, { BiometricType } from './class/biometrics'; import { StackActions, useNavigation } from '@react-navigation/native'; import { BlueStorageContext } from './blue_modules/storage-context'; import { isHandset } from './blue_modules/environment'; import triggerHapticFeedback, { HapticFeedbackTypes } from './blue_modules/hapticFeedback'; import SafeArea from './components/SafeArea'; enum AuthType { Encrypted, Biometrics, None, } type State = { auth: { type: AuthType; detail: keyof typeof BiometricType | undefined; }; isAuthenticating: boolean; }; const SET_AUTH = 'SET_AUTH'; const SET_IS_AUTHENTICATING = 'SET_IS_AUTHENTICATING'; type Action = | { type: typeof SET_AUTH; payload: { type: AuthType; detail: keyof typeof BiometricType | undefined } } | { type: typeof SET_IS_AUTHENTICATING; payload: boolean }; const initialState: State = { auth: { type: AuthType.None, detail: undefined, }, isAuthenticating: false, }; function reducer(state: State, action: Action): State { switch (action.type) { case SET_AUTH: return { ...state, auth: action.payload }; case SET_IS_AUTHENTICATING: return { ...state, isAuthenticating: action.payload }; default: return state; } } const { SplashScreen } = NativeModules; const UnlockWith: React.FC = () => { const [state, dispatch] = useReducer(reducer, initialState); const isUnlockingWallets = useRef(false); const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useContext(BlueStorageContext); const navigation = useNavigation(); const colorScheme = useColorScheme(); const successfullyAuthenticated = useCallback(() => { setWalletsInitialized(true); navigation.dispatch(StackActions.replace(isHandset ? 'Navigation' : 'DrawerRoot')); isUnlockingWallets.current = false; }, [setWalletsInitialized, navigation]); const unlockWithBiometrics = useCallback(async () => { if (isUnlockingWallets.current || state.isAuthenticating) return; isUnlockingWallets.current = true; dispatch({ type: SET_IS_AUTHENTICATING, payload: true }); if (await Biometric.unlockWithBiometrics()) { await startAndDecrypt(); successfullyAuthenticated(); } dispatch({ type: SET_IS_AUTHENTICATING, payload: false }); isUnlockingWallets.current = false; }, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]); const unlockWithKey = useCallback(async () => { if (isUnlockingWallets.current || state.isAuthenticating) return; isUnlockingWallets.current = true; dispatch({ type: SET_IS_AUTHENTICATING, payload: true }); if (await startAndDecrypt()) { triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); successfullyAuthenticated(); } else { dispatch({ type: SET_IS_AUTHENTICATING, payload: false }); isUnlockingWallets.current = false; } }, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]); useEffect(() => { SplashScreen?.dismissSplashScreen(); const startUnlock = async () => { const storageIsEncrypted = await isStorageEncrypted(); const isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled(); const biometricType = isBiometricUseCapableAndEnabled ? await Biometric.biometricType() : undefined; if (storageIsEncrypted) { dispatch({ type: SET_AUTH, payload: { type: AuthType.Encrypted, detail: undefined } }); unlockWithKey(); } else if (isBiometricUseCapableAndEnabled) { dispatch({ type: SET_AUTH, payload: { type: AuthType.Biometrics, detail: biometricType } }); unlockWithBiometrics(); } else { dispatch({ type: SET_AUTH, payload: { type: AuthType.None, detail: undefined } }); unlockWithKey(); } }; startUnlock(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const renderUnlockOptions = () => { const color = colorScheme === 'dark' ? '#FFFFFF' : '#000000'; if (state.isAuthenticating) { return ; } else { switch (state.auth.type) { case AuthType.Biometrics: if (state.auth.detail === 'TouchID' || state.auth.detail === 'Biometrics') { return ( ); } else if (state.auth.detail === 'FaceID') { return ( ); } return null; case AuthType.Encrypted: return ( ); default: return null; } } }; return ( {renderUnlockOptions()} ); }; const styles = StyleSheet.create({ root: { flex: 1, justifyContent: 'space-between', }, container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, biometricRow: { justifyContent: 'center', flexDirection: 'row', width: 64, height: 64, alignSelf: 'center', marginBottom: 20, }, icon: { width: 64, height: 64, }, logoImage: { width: 100, height: 75, alignSelf: 'center', }, }); export default UnlockWith;