feat: mobile app package (#1075)
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import { UnlockTiming } from '@Lib/ApplicationState'
|
||||
import { MobileDeviceInterface } 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 { 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<ModalStackNavigationProp<typeof SCREEN_SETTINGS>['navigation']>()
|
||||
// Context
|
||||
const application = useContext(ApplicationContext)
|
||||
|
||||
// State
|
||||
const [encryptionPolicy, setEncryptionPolicy] = useState(() => application?.getStorageEncryptionPolicy())
|
||||
const [encryptionPolictChangeInProgress, setEncryptionPolictChangeInProgress] = useState(false)
|
||||
const [hasScreenshotPrivacy, setHasScreenshotPrivacy] = useState<boolean | undefined>(false)
|
||||
const [hasBiometrics, setHasBiometrics] = useState(false)
|
||||
const [supportsBiometrics, setSupportsBiometrics] = useState(false)
|
||||
const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() =>
|
||||
application!.getAppState().getBiometricsTimingOptions(),
|
||||
)
|
||||
const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() =>
|
||||
application!.getAppState().getPasscodeTimingOptions(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
const getHasScreenshotPrivacy = async () => {
|
||||
const hasScreenshotPrivacyEnabled = await application?.getAppState().screenshotPrivacyEnabled
|
||||
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 MobileDeviceInterface
|
||||
).getDeviceBiometricsAvailability()
|
||||
if (mounted) {
|
||||
setSupportsBiometrics(hasBiometricsAvailable)
|
||||
}
|
||||
}
|
||||
void hasBiometricsSupport()
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [application])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (props.hasPasscode) {
|
||||
setPasscodeTimingOptions(() => application!.getAppState().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: UnlockTiming) => {
|
||||
await application?.getAppState().setBiometricsTiming(timing)
|
||||
setBiometricsTimingOptions(() => application!.getAppState().getBiometricsTimingOptions())
|
||||
}
|
||||
|
||||
const setPasscodeTiming = async (timing: UnlockTiming) => {
|
||||
await application?.getAppState().setPasscodeTiming(timing)
|
||||
setPasscodeTimingOptions(() => application!.getAppState().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(UnlockTiming.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 (
|
||||
<TableSection>
|
||||
<SectionHeader title={props.title} />
|
||||
|
||||
<ButtonCell first leftAligned title={storageEncryptionTitle} onPress={toggleEncryptionPolicy}>
|
||||
<Title>{storageSubText}</Title>
|
||||
</ButtonCell>
|
||||
|
||||
<ButtonCell leftAligned title={screenshotPrivacyTitle} onPress={onScreenshotPrivacyPress} />
|
||||
|
||||
<ButtonCell leftAligned title={passcodeTitle} onPress={onPasscodePress} />
|
||||
|
||||
<ButtonCell
|
||||
last={!hasBiometrics && !props.hasPasscode}
|
||||
disabled={!supportsBiometrics}
|
||||
leftAligned
|
||||
title={biometricTitle}
|
||||
onPress={onBiometricsPress}
|
||||
/>
|
||||
|
||||
{props.hasPasscode && (
|
||||
<SectionedOptionsTableCell
|
||||
leftAligned
|
||||
title={'Require Passcode'}
|
||||
options={passcodeTimingOptions}
|
||||
onPress={(option: Option) => setPasscodeTiming(option.key as UnlockTiming)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasBiometrics && (
|
||||
<SectionedOptionsTableCell
|
||||
leftAligned
|
||||
title={'Require Biometrics'}
|
||||
options={biometricsTimingOptions}
|
||||
onPress={(option: Option) => setBiometricsTiming(option.key as UnlockTiming)}
|
||||
/>
|
||||
)}
|
||||
</TableSection>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user