2024-02-24 16:08:10 +01:00
|
|
|
import React, { useCallback, useContext, useEffect, useReducer, useRef } from 'react';
|
2024-05-16 00:06:55 +02:00
|
|
|
import { View, Image, ActivityIndicator, StyleSheet } from 'react-native';
|
2024-03-11 01:29:37 +01:00
|
|
|
import Biometric, { BiometricType } from '../class/biometrics';
|
|
|
|
import { BlueStorageContext } from '../blue_modules/storage-context';
|
|
|
|
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
|
|
|
import SafeArea from '../components/SafeArea';
|
|
|
|
import { BlueTextCentered } from '../BlueComponents';
|
|
|
|
import loc from '../loc';
|
2024-03-13 22:48:31 +01:00
|
|
|
import Button from '../components/Button';
|
2024-02-24 16:08:10 +01:00
|
|
|
|
|
|
|
enum AuthType {
|
|
|
|
Encrypted,
|
|
|
|
Biometrics,
|
|
|
|
None,
|
2024-03-11 01:23:58 +01:00
|
|
|
BiometricsUnavailable,
|
2024-02-24 16:08:10 +01:00
|
|
|
}
|
2024-02-12 12:36:51 +01:00
|
|
|
|
|
|
|
type State = {
|
2024-02-24 16:08:10 +01:00
|
|
|
auth: {
|
|
|
|
type: AuthType;
|
|
|
|
detail: keyof typeof BiometricType | undefined;
|
|
|
|
};
|
2024-02-12 12:36:51 +01:00
|
|
|
isAuthenticating: boolean;
|
|
|
|
};
|
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
const SET_AUTH = 'SET_AUTH';
|
2024-02-12 12:36:51 +01:00
|
|
|
const SET_IS_AUTHENTICATING = 'SET_IS_AUTHENTICATING';
|
|
|
|
|
|
|
|
type Action =
|
2024-02-24 16:08:10 +01:00
|
|
|
| { type: typeof SET_AUTH; payload: { type: AuthType; detail: keyof typeof BiometricType | undefined } }
|
2024-02-12 12:36:51 +01:00
|
|
|
| { type: typeof SET_IS_AUTHENTICATING; payload: boolean };
|
|
|
|
|
|
|
|
const initialState: State = {
|
2024-02-24 16:08:10 +01:00
|
|
|
auth: {
|
|
|
|
type: AuthType.None,
|
|
|
|
detail: undefined,
|
|
|
|
},
|
2024-02-12 12:36:51 +01:00
|
|
|
isAuthenticating: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
function reducer(state: State, action: Action): State {
|
|
|
|
switch (action.type) {
|
2024-02-24 16:08:10 +01:00
|
|
|
case SET_AUTH:
|
|
|
|
return { ...state, auth: action.payload };
|
2024-02-12 12:36:51 +01:00
|
|
|
case SET_IS_AUTHENTICATING:
|
|
|
|
return { ...state, isAuthenticating: action.payload };
|
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const UnlockWith: React.FC = () => {
|
|
|
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
|
|
const isUnlockingWallets = useRef(false);
|
|
|
|
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useContext(BlueStorageContext);
|
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
const successfullyAuthenticated = useCallback(() => {
|
2024-02-12 12:36:51 +01:00
|
|
|
setWalletsInitialized(true);
|
|
|
|
isUnlockingWallets.current = false;
|
2024-03-07 16:55:20 +01:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, []);
|
2024-02-12 12:36:51 +01:00
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
const unlockWithBiometrics = useCallback(async () => {
|
2024-02-12 12:36:51 +01:00
|
|
|
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;
|
2024-02-24 16:08:10 +01:00
|
|
|
}, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]);
|
2024-02-12 12:36:51 +01:00
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
const unlockWithKey = useCallback(async () => {
|
2024-02-12 12:36:51 +01:00
|
|
|
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;
|
|
|
|
}
|
2024-02-24 16:08:10 +01:00
|
|
|
}, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]);
|
2024-02-12 12:36:51 +01:00
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
useEffect(() => {
|
|
|
|
const startUnlock = async () => {
|
2024-02-12 12:36:51 +01:00
|
|
|
const storageIsEncrypted = await isStorageEncrypted();
|
2024-02-19 11:17:44 +01:00
|
|
|
const isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
2024-02-24 16:08:10 +01:00
|
|
|
const biometricType = isBiometricUseCapableAndEnabled ? await Biometric.biometricType() : undefined;
|
2024-03-11 01:23:58 +01:00
|
|
|
const biometricsUseEnabled = await Biometric.isBiometricUseEnabled();
|
2024-02-12 12:36:51 +01:00
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
if (storageIsEncrypted) {
|
|
|
|
dispatch({ type: SET_AUTH, payload: { type: AuthType.Encrypted, detail: undefined } });
|
2024-02-12 12:36:51 +01:00
|
|
|
unlockWithKey();
|
2024-02-24 16:08:10 +01:00
|
|
|
} else if (isBiometricUseCapableAndEnabled) {
|
|
|
|
dispatch({ type: SET_AUTH, payload: { type: AuthType.Biometrics, detail: biometricType } });
|
2024-02-12 12:36:51 +01:00
|
|
|
unlockWithBiometrics();
|
2024-03-11 01:23:58 +01:00
|
|
|
} else if (biometricsUseEnabled && biometricType === undefined) {
|
2024-03-15 23:38:32 +01:00
|
|
|
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
2024-03-11 01:23:58 +01:00
|
|
|
dispatch({ type: SET_AUTH, payload: { type: AuthType.BiometricsUnavailable, detail: undefined } });
|
2024-02-24 16:08:10 +01:00
|
|
|
} else {
|
|
|
|
dispatch({ type: SET_AUTH, payload: { type: AuthType.None, detail: undefined } });
|
|
|
|
unlockWithKey();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
startUnlock();
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, []);
|
|
|
|
|
2024-03-13 18:26:22 +01:00
|
|
|
const onUnlockPressed = () => {
|
|
|
|
if (state.auth.type === AuthType.Biometrics) {
|
|
|
|
unlockWithBiometrics();
|
|
|
|
} else {
|
|
|
|
unlockWithKey();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-02-24 16:08:10 +01:00
|
|
|
const renderUnlockOptions = () => {
|
|
|
|
if (state.isAuthenticating) {
|
|
|
|
return <ActivityIndicator />;
|
|
|
|
} else {
|
|
|
|
switch (state.auth.type) {
|
|
|
|
case AuthType.Biometrics:
|
|
|
|
case AuthType.Encrypted:
|
2024-03-13 18:26:22 +01:00
|
|
|
return <Button onPress={onUnlockPressed} title={loc._.unlock} />;
|
2024-03-11 01:23:58 +01:00
|
|
|
case AuthType.BiometricsUnavailable:
|
|
|
|
return <BlueTextCentered>{loc.settings.biometrics_no_longer_available}</BlueTextCentered>;
|
2024-02-24 16:08:10 +01:00
|
|
|
default:
|
|
|
|
return null;
|
2024-02-12 12:36:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<SafeArea style={styles.root}>
|
|
|
|
<View style={styles.container}>
|
2024-03-11 01:29:37 +01:00
|
|
|
<Image source={require('../img/icon.png')} style={styles.logoImage} resizeMode="contain" />
|
2024-02-12 12:36:51 +01:00
|
|
|
</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',
|
2024-03-15 23:38:32 +01:00
|
|
|
width: 300,
|
2024-03-17 05:20:33 +01:00
|
|
|
minHeight: 60,
|
2024-02-12 12:36:51 +01:00
|
|
|
alignSelf: 'center',
|
|
|
|
marginBottom: 20,
|
2024-03-11 01:23:58 +01:00
|
|
|
paddingHorizontal: 20,
|
2024-02-12 12:36:51 +01:00
|
|
|
},
|
|
|
|
logoImage: {
|
|
|
|
width: 100,
|
|
|
|
height: 75,
|
|
|
|
alignSelf: 'center',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
export default UnlockWith;
|