import React, { useCallback, useEffect, useReducer } from 'react';
import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import ListItem from '../../components/ListItem';
import { useTheme } from '../../components/themes';
import prompt from '../../helpers/prompt';
import { unlockWithBiometrics, useBiometrics } from '../../hooks/useBiometrics';
import loc from '../../loc';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { useStorage } from '../../hooks/context/useStorage';
import { popToTop } from '../../NavigationService';
enum ActionType {
SetLoading = 'SET_LOADING',
SetStorageEncryptedSwitch = 'SET_STORAGE_ENCRYPTED_SWITCH',
SetDeviceBiometricCapable = 'SET_DEVICE_BIOMETRIC_CAPABLE',
SetCurrentLoadingSwitch = 'SET_CURRENT_LOADING_SWITCH',
}
interface State {
isLoading: boolean;
storageIsEncryptedSwitchEnabled: boolean;
deviceBiometricCapable: boolean;
currentLoadingSwitch: string | null;
}
interface Action {
type: ActionType;
payload?: any;
}
const initialState: State = {
isLoading: true,
storageIsEncryptedSwitchEnabled: false,
deviceBiometricCapable: false,
currentLoadingSwitch: null,
};
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ActionType.SetLoading:
return { ...state, isLoading: action.payload };
case ActionType.SetStorageEncryptedSwitch:
return { ...state, storageIsEncryptedSwitchEnabled: action.payload };
case ActionType.SetDeviceBiometricCapable:
return { ...state, deviceBiometricCapable: action.payload };
case ActionType.SetCurrentLoadingSwitch:
return { ...state, currentLoadingSwitch: action.payload };
default:
return state;
}
};
const EncryptStorage = () => {
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useStorage();
const { isDeviceBiometricCapable, biometricEnabled, setBiometricUseEnabled, deviceBiometricType } = useBiometrics();
const [state, dispatch] = useReducer(reducer, initialState);
const { navigate } = useExtendedNavigation();
const { colors } = useTheme();
const styleHooks = StyleSheet.create({
root: {
backgroundColor: colors.background,
},
headerText: {
color: colors.foregroundColor,
},
});
const initializeState = useCallback(async () => {
const isStorageEncryptedSwitchEnabled = await isStorageEncrypted();
const isDeviceBiometricCapableSync = await isDeviceBiometricCapable();
dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: isStorageEncryptedSwitchEnabled });
dispatch({ type: ActionType.SetDeviceBiometricCapable, payload: isDeviceBiometricCapableSync });
dispatch({ type: ActionType.SetLoading, payload: false });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
initializeState();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleDecryptStorage = async () => {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'decrypt' });
const password = await prompt(loc.settings.password, loc._.storage_is_encrypted).catch(() => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
});
if (!password) {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
return;
}
try {
await decryptStorage(password);
await saveToDisk();
popToTop();
} catch (e) {
if (password) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc._.bad_password });
}
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: await isStorageEncrypted() });
}
};
const onEncryptStorageSwitch = async (value: boolean) => {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'encrypt' });
dispatch({ type: ActionType.SetLoading, payload: true });
if (value) {
let p1 = await prompt(loc.settings.password, loc.settings.password_explain).catch(() => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
p1 = undefined;
});
if (!p1) {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
return;
}
const p2 = await prompt(loc.settings.password, loc.settings.retype_password).catch(() => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
});
if (p1 === p2) {
await encryptStorage(p1);
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: await isStorageEncrypted() });
saveToDisk();
} else {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc.settings.passwords_do_not_match });
}
} else {
Alert.alert(
loc.settings.encrypt_decrypt,
loc.settings.encrypt_decrypt_q,
[
{
text: loc._.cancel,
style: 'cancel',
onPress: () => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
},
},
{
text: loc._.ok,
style: 'destructive',
onPress: handleDecryptStorage,
},
],
{ cancelable: false },
);
}
};
const onUseBiometricSwitch = async (value: boolean) => {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'biometric' });
if (await unlockWithBiometrics()) {
setBiometricUseEnabled(value);
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
} else {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
}
};
const navigateToPlausibleDeniability = () => {
navigate('PlausibleDeniability');
};
const renderPasscodeExplanation = () => {
let isCapable = true;
if (Platform.OS === 'android') {
if (Platform.Version < 30) {
isCapable = false;
}
}
return isCapable ? (
<>
{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType! })}
>
) : null;
};
return (
{state.deviceBiometricCapable && (
<>
{loc.settings.biometrics}
{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType! })}
{renderPasscodeExplanation()}
>
)}
{loc.settings.encrypt_tstorage}
{state.storageIsEncryptedSwitchEnabled && (
)}
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
paddingTop: { paddingTop: 19 },
headerText: {
fontWeight: 'bold',
fontSize: 30,
marginLeft: 17,
},
row: { minHeight: 60 },
});
export default EncryptStorage;