mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-20 02:09:10 +01:00
197 lines
6.5 KiB
TypeScript
197 lines
6.5 KiB
TypeScript
import React, { 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 { NavigationProp, RouteProp, StackActions, useNavigation, useRoute } 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';
|
|
type RootStackParamList = {
|
|
UnlockWith: { unlockOnComponentMount?: boolean };
|
|
};
|
|
|
|
type State = {
|
|
biometricType: BiometricType | undefined;
|
|
isStorageEncryptedEnabled: boolean;
|
|
isAuthenticating: boolean;
|
|
};
|
|
|
|
const SET_BIOMETRIC_TYPE = 'SET_BIOMETRIC_TYPE';
|
|
const SET_IS_STORAGE_ENCRYPTED_ENABLED = 'SET_IS_STORAGE_ENCRYPTED_ENABLED';
|
|
const SET_IS_AUTHENTICATING = 'SET_IS_AUTHENTICATING';
|
|
|
|
type Action =
|
|
| { type: typeof SET_BIOMETRIC_TYPE; payload: BiometricType | undefined }
|
|
| { type: typeof SET_IS_STORAGE_ENCRYPTED_ENABLED; payload: boolean }
|
|
| { type: typeof SET_IS_AUTHENTICATING; payload: boolean };
|
|
|
|
const initialState: State = {
|
|
biometricType: undefined,
|
|
isStorageEncryptedEnabled: false,
|
|
isAuthenticating: false,
|
|
};
|
|
|
|
function reducer(state: State, action: Action): State {
|
|
switch (action.type) {
|
|
case SET_BIOMETRIC_TYPE:
|
|
return { ...state, biometricType: action.payload };
|
|
case SET_IS_STORAGE_ENCRYPTED_ENABLED:
|
|
return { ...state, isStorageEncryptedEnabled: 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<NavigationProp<RootStackParamList, 'UnlockWith'>>();
|
|
const route = useRoute<RouteProp<RootStackParamList, 'UnlockWith'>>();
|
|
const { unlockOnComponentMount } = route.params;
|
|
const colorScheme = useColorScheme();
|
|
|
|
useEffect(() => {
|
|
SplashScreen?.dismissSplashScreen();
|
|
startUnlock();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const successfullyAuthenticated = () => {
|
|
setWalletsInitialized(true);
|
|
navigation.dispatch(StackActions.replace(isHandset ? 'Navigation' : 'DrawerRoot'));
|
|
isUnlockingWallets.current = false;
|
|
};
|
|
|
|
const unlockWithBiometrics = 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;
|
|
};
|
|
|
|
const unlockWithKey = 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;
|
|
}
|
|
};
|
|
|
|
const renderUnlockOptions = () => {
|
|
if (state.isAuthenticating) {
|
|
return <ActivityIndicator />;
|
|
} else {
|
|
const color = colorScheme === 'dark' ? '#FFFFFF' : '#000000';
|
|
if (
|
|
(state.biometricType === BiometricType.TouchID || state.biometricType === BiometricType.Biometrics) &&
|
|
!state.isStorageEncryptedEnabled
|
|
) {
|
|
return (
|
|
<TouchableOpacity accessibilityRole="button" disabled={state.isAuthenticating} onPress={unlockWithBiometrics}>
|
|
<Icon name="fingerprint" size={64} type="font-awesome5" color={color} />
|
|
</TouchableOpacity>
|
|
);
|
|
} else if (state.biometricType === BiometricType.FaceID && !state.isStorageEncryptedEnabled) {
|
|
return (
|
|
<TouchableOpacity accessibilityRole="button" disabled={state.isAuthenticating} onPress={unlockWithBiometrics}>
|
|
<Image
|
|
source={colorScheme === 'dark' ? require('./img/faceid-default.png') : require('./img/faceid-dark.png')}
|
|
style={styles.icon}
|
|
/>
|
|
</TouchableOpacity>
|
|
);
|
|
} else if (state.isStorageEncryptedEnabled) {
|
|
return (
|
|
<TouchableOpacity accessibilityRole="button" disabled={state.isAuthenticating} onPress={unlockWithKey}>
|
|
<Icon name="lock" size={64} type="font-awesome5" color={color} />
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
const startUnlock = async () => {
|
|
if (unlockOnComponentMount) {
|
|
const storageIsEncrypted = await isStorageEncrypted();
|
|
dispatch({ type: SET_IS_STORAGE_ENCRYPTED_ENABLED, payload: storageIsEncrypted });
|
|
const rawType = await Biometric.biometricType();
|
|
|
|
let type;
|
|
if (rawType === 'Biometrics') {
|
|
type = BiometricType.Biometrics;
|
|
} else if (rawType === 'Touch ID') {
|
|
type = BiometricType.TouchID;
|
|
} else if (rawType === 'Face ID') {
|
|
type = BiometricType.FaceID;
|
|
}
|
|
|
|
dispatch({ type: SET_BIOMETRIC_TYPE, payload: type });
|
|
|
|
if (!type || storageIsEncrypted) {
|
|
unlockWithKey();
|
|
} else {
|
|
unlockWithBiometrics();
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<SafeArea style={styles.root}>
|
|
<View style={styles.container}>
|
|
<Image source={require('./img/icon.png')} style={styles.logoImage} resizeMode="contain" />
|
|
</View>
|
|
<View style={styles.biometricRow}>{renderUnlockOptions()}</View>
|
|
</SafeArea>
|
|
);
|
|
};
|
|
|
|
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;
|