BlueWallet/screen/UnlockWith.tsx

184 lines
5.7 KiB
TypeScript
Raw Normal View History

2024-05-18 00:34:39 +02:00
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
2024-05-20 11:54:13 +02:00
import { ActivityIndicator, Image, StyleSheet, View } from 'react-native';
2024-03-11 01:29:37 +01:00
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
2024-05-20 11:54:13 +02:00
import { useStorage } from '../blue_modules/storage-context';
2024-03-11 01:29:37 +01:00
import { BlueTextCentered } from '../BlueComponents';
2024-03-13 22:48:31 +01:00
import Button from '../components/Button';
2024-05-20 11:54:13 +02:00
import SafeArea from '../components/SafeArea';
2024-05-18 00:34:39 +02:00
import { BiometricType, useBiometrics } from '../hooks/useBiometrics';
2024-05-20 11:54:13 +02:00
import loc from '../loc';
enum AuthType {
Encrypted,
Biometrics,
None,
2024-03-11 01:23:58 +01:00
BiometricsUnavailable,
}
2024-02-12 12:36:51 +01:00
type State = {
auth: {
type: AuthType;
detail: keyof typeof BiometricType | undefined;
};
2024-02-12 12:36:51 +01:00
isAuthenticating: boolean;
};
const SET_AUTH = 'SET_AUTH';
2024-02-12 12:36:51 +01:00
const SET_IS_AUTHENTICATING = 'SET_IS_AUTHENTICATING';
type Action =
| { 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 = {
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) {
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);
2024-05-18 00:34:39 +02:00
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useStorage();
const { deviceBiometricType, unlockWithBiometrics, isBiometricUseCapableAndEnabled, isBiometricUseEnabled } = useBiometrics();
2024-02-12 12:36:51 +01:00
useEffect(() => {
setWalletsInitialized(false);
}, [setWalletsInitialized]);
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-05-18 00:34:39 +02:00
const unlockUsingBiometrics = 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 });
2024-05-18 00:34:39 +02:00
if (await unlockWithBiometrics()) {
2024-02-12 12:36:51 +01:00
await startAndDecrypt();
successfullyAuthenticated();
}
dispatch({ type: SET_IS_AUTHENTICATING, payload: false });
isUnlockingWallets.current = false;
2024-05-18 00:34:39 +02:00
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.isAuthenticating]);
2024-02-12 12:36:51 +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;
}
}, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]);
2024-02-12 12:36:51 +01:00
useEffect(() => {
const startUnlock = async () => {
2024-02-12 12:36:51 +01:00
const storageIsEncrypted = await isStorageEncrypted();
2024-05-18 00:34:39 +02:00
const biometricUseCapableAndEnabled = await isBiometricUseCapableAndEnabled();
const biometricsUseEnabled = await isBiometricUseEnabled();
const biometricType = biometricUseCapableAndEnabled ? deviceBiometricType : undefined;
2024-02-12 12:36:51 +01:00
if (storageIsEncrypted) {
dispatch({ type: SET_AUTH, payload: { type: AuthType.Encrypted, detail: undefined } });
2024-02-12 12:36:51 +01:00
unlockWithKey();
2024-05-18 00:34:39 +02:00
} else if (biometricUseCapableAndEnabled) {
dispatch({ type: SET_AUTH, payload: { type: AuthType.Biometrics, detail: biometricType } });
2024-05-21 23:57:09 +02:00
unlockUsingBiometrics();
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 } });
} else {
dispatch({ type: SET_AUTH, payload: { type: AuthType.None, detail: undefined } });
unlockWithKey();
}
};
startUnlock();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onUnlockPressed = () => {
if (state.auth.type === AuthType.Biometrics) {
2024-05-18 00:34:39 +02:00
unlockUsingBiometrics();
} else {
unlockWithKey();
}
};
const renderUnlockOptions = () => {
if (state.isAuthenticating) {
return <ActivityIndicator />;
} else {
switch (state.auth.type) {
case AuthType.Biometrics:
case AuthType.Encrypted:
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>;
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;