import { MobileDevice } from '@Lib/Interface' import { useFocusEffect, useNavigation } from '@react-navigation/native' import { ApplicationContext } from '@Root/ApplicationContext' import { ButtonCell } from '@Root/Components/ButtonCell' import { Option, SectionedOptionsTableCell } from '@Root/Components/SectionedOptionsTableCell' import { SectionHeader } from '@Root/Components/SectionHeader' import { TableSection } from '@Root/Components/TableSection' import { ModalStackNavigationProp } from '@Root/ModalStack' import { SCREEN_INPUT_MODAL_PASSCODE, SCREEN_SETTINGS } from '@Root/Screens/screens' import { MobileUnlockTiming, StorageEncryptionPolicy } from '@standardnotes/snjs' import React, { useCallback, useContext, useEffect, useState } from 'react' import { Platform } from 'react-native' import { Title } from './SecuritySection.styled' type Props = { title: string hasPasscode: boolean encryptionAvailable: boolean updateProtectionsAvailable: (...args: unknown[]) => unknown } export const SecuritySection = (props: Props) => { const navigation = useNavigation['navigation']>() // Context const application = useContext(ApplicationContext) // State const [encryptionPolicy, setEncryptionPolicy] = useState(() => application?.getStorageEncryptionPolicy()) const [encryptionPolictChangeInProgress, setEncryptionPolictChangeInProgress] = useState(false) const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState(false) const [hasBiometrics, setHasBiometrics] = useState(false) const [supportsBiometrics, setSupportsBiometrics] = useState(false) const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() => application!.getBiometricsTimingOptions(), ) const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() => application!.getPasscodeTimingOptions()) useEffect(() => { let mounted = true const getHasScreenshotPrivacy = async () => { const hasScreenshotPrivacyEnabled = (await application?.getMobileScreenshotPrivacyEnabled()) ?? true if (mounted) { setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled) } } void getHasScreenshotPrivacy() const getHasBiometrics = async () => { const appHasBiometrics = await application!.hasBiometrics() if (mounted) { setHasBiometrics(appHasBiometrics) } } void getHasBiometrics() const hasBiometricsSupport = async () => { const hasBiometricsAvailable = await ( application?.deviceInterface as MobileDevice ).getDeviceBiometricsAvailability() if (mounted) { setSupportsBiometrics(hasBiometricsAvailable) } } void hasBiometricsSupport() return () => { mounted = false } }, [application]) useFocusEffect( useCallback(() => { if (props.hasPasscode) { setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions()) } }, [application, props.hasPasscode]), ) const toggleEncryptionPolicy = async () => { if (!props.encryptionAvailable) { return } if (encryptionPolicy === StorageEncryptionPolicy.Default) { setEncryptionPolictChangeInProgress(true) setEncryptionPolicy(StorageEncryptionPolicy.Disabled) await application?.setStorageEncryptionPolicy(StorageEncryptionPolicy.Disabled) setEncryptionPolictChangeInProgress(false) } else if (encryptionPolicy === StorageEncryptionPolicy.Disabled) { setEncryptionPolictChangeInProgress(true) setEncryptionPolicy(StorageEncryptionPolicy.Default) await application?.setStorageEncryptionPolicy(StorageEncryptionPolicy.Default) setEncryptionPolictChangeInProgress(false) } } // State const storageEncryptionTitle = props.encryptionAvailable ? encryptionPolicy === StorageEncryptionPolicy.Default ? 'Disable Storage Encryption' : 'Enable Storage Encryption' : 'Storage Encryption' let storageSubText = "Encrypts your data before saving to your device's local storage." if (props.encryptionAvailable) { storageSubText += encryptionPolicy === StorageEncryptionPolicy.Default ? ' Disable to improve app start-up speed.' : ' May decrease app start-up speed.' } else { storageSubText += ' Sign in, register, or add a local passcode to enable this option.' } if (encryptionPolictChangeInProgress) { storageSubText = 'Applying changes...' } const screenshotPrivacyFeatureText = Platform.OS === 'ios' ? 'Multitasking Privacy' : 'Multitasking/Screenshot Privacy' const screenshotPrivacyTitle = hasScreenshotPrivacy ? `Disable ${screenshotPrivacyFeatureText}` : `Enable ${screenshotPrivacyFeatureText}` const passcodeTitle = props.hasPasscode ? 'Disable Passcode Lock' : 'Enable Passcode Lock' const biometricTitle = hasBiometrics ? 'Disable Biometrics Lock' : 'Enable Biometrics Lock' const setBiometricsTiming = async (timing: MobileUnlockTiming) => { await application?.getAppState().setBiometricsTiming(timing) setBiometricsTimingOptions(() => application!.getBiometricsTimingOptions()) } const setPasscodeTiming = async (timing: MobileUnlockTiming) => { await application?.getAppState().setPasscodeTiming(timing) setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions()) } const onScreenshotPrivacyPress = async () => { const enable = !hasScreenshotPrivacy setHasScreenshotPrivacy(enable) await application?.getAppState().setScreenshotPrivacyEnabled(enable) } const onPasscodePress = async () => { if (props.hasPasscode) { void disableAuthentication('passcode') } else { navigation.push(SCREEN_INPUT_MODAL_PASSCODE) } } const onBiometricsPress = async () => { if (hasBiometrics) { void disableAuthentication('biometrics') } else { setHasBiometrics(true) await application?.enableBiometrics() await setBiometricsTiming(MobileUnlockTiming.OnQuit) props.updateProtectionsAvailable() } } const disableBiometrics = useCallback(async () => { if (await application?.disableBiometrics()) { setHasBiometrics(false) props.updateProtectionsAvailable() } }, [application, props]) const disablePasscode = useCallback(async () => { const hasAccount = Boolean(application?.hasAccount()) let message if (hasAccount) { message = 'Are you sure you want to disable your local passcode? This will not affect your encryption status, as your data is currently being encrypted through your sync account keys.' } else { message = 'Are you sure you want to disable your local passcode? This will disable encryption on your data.' } const confirmed = await application?.alertService?.confirm( message, 'Disable Passcode', 'Disable Passcode', undefined, ) if (confirmed) { await application?.removePasscode() } }, [application]) const disableAuthentication = useCallback( async (authenticationMethod: 'passcode' | 'biometrics') => { switch (authenticationMethod) { case 'biometrics': { void disableBiometrics() break } case 'passcode': { void disablePasscode() break } } }, [disableBiometrics, disablePasscode], ) return ( {storageSubText} {props.hasPasscode && ( setPasscodeTiming(option.key as MobileUnlockTiming)} /> )} {hasBiometrics && ( setBiometricsTiming(option.key as MobileUnlockTiming)} /> )} ) }