diff --git a/.eslintignore b/.eslintignore index e64e5ecea..a04dd0f0f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -990,6 +990,12 @@ packages/app-mobile/components/SelectDateTimeDialog.js.map packages/app-mobile/components/SideMenu.d.ts packages/app-mobile/components/SideMenu.js packages/app-mobile/components/SideMenu.js.map +packages/app-mobile/components/biometrics/BiometricPopup.d.ts +packages/app-mobile/components/biometrics/BiometricPopup.js +packages/app-mobile/components/biometrics/BiometricPopup.js.map +packages/app-mobile/components/biometrics/sensorInfo.d.ts +packages/app-mobile/components/biometrics/sensorInfo.js +packages/app-mobile/components/biometrics/sensorInfo.js.map packages/app-mobile/components/getResponsiveValue.d.ts packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.js.map diff --git a/.gitignore b/.gitignore index 1d1b9de1a..f71d922ab 100644 --- a/.gitignore +++ b/.gitignore @@ -978,6 +978,12 @@ packages/app-mobile/components/SelectDateTimeDialog.js.map packages/app-mobile/components/SideMenu.d.ts packages/app-mobile/components/SideMenu.js packages/app-mobile/components/SideMenu.js.map +packages/app-mobile/components/biometrics/BiometricPopup.d.ts +packages/app-mobile/components/biometrics/BiometricPopup.js +packages/app-mobile/components/biometrics/BiometricPopup.js.map +packages/app-mobile/components/biometrics/sensorInfo.d.ts +packages/app-mobile/components/biometrics/sensorInfo.js +packages/app-mobile/components/biometrics/sensorInfo.js.map packages/app-mobile/components/getResponsiveValue.d.ts packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.js.map diff --git a/packages/app-mobile/android/app/src/main/AndroidManifest.xml b/packages/app-mobile/android/app/src/main/AndroidManifest.xml index c590d1a7b..dbdcd8303 100644 --- a/packages/app-mobile/android/app/src/main/AndroidManifest.xml +++ b/packages/app-mobile/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + diff --git a/packages/app-mobile/android/build.gradle b/packages/app-mobile/android/build.gradle index 93b640201..c90394821 100644 --- a/packages/app-mobile/android/build.gradle +++ b/packages/app-mobile/android/build.gradle @@ -46,6 +46,11 @@ allprojects { excludeGroup "com.facebook.react" } } + maven { + // Required by react-native-fingerprint-scanner + // https://github.com/hieuvp/react-native-fingerprint-scanner/issues/192 + url "https://maven.aliyun.com/repository/jcenter" + } google() maven { url 'https://www.jitpack.io' } } diff --git a/packages/app-mobile/components/biometrics/BiometricPopup.tsx b/packages/app-mobile/components/biometrics/BiometricPopup.tsx new file mode 100644 index 000000000..db501bdfb --- /dev/null +++ b/packages/app-mobile/components/biometrics/BiometricPopup.tsx @@ -0,0 +1,89 @@ +const React = require('react'); +import Setting from '@joplin/lib/models/Setting'; +import { useEffect, useMemo, useState } from 'react'; +import { View, Dimensions, Alert, Button } from 'react-native'; +import FingerprintScanner from 'react-native-fingerprint-scanner'; +import { SensorInfo } from './sensorInfo'; +import { _ } from '@joplin/lib/locale'; + +interface Props { + themeId: number; + sensorInfo: SensorInfo; +} + +export default (props: Props) => { + const [initialPromptDone, setInitialPromptDone] = useState(false); // Setting.value('security.biometricsInitialPromptDone')); + const [display, setDisplay] = useState(!!props.sensorInfo.supportedSensors && (props.sensorInfo.enabled || !initialPromptDone)); + const [tryBiometricsCheck, setTryBiometricsCheck] = useState(initialPromptDone); + + useEffect(() => { + if (!display || !tryBiometricsCheck) return; + + const biometricsCheck = async () => { + try { + await FingerprintScanner.authenticate({ description: _('Verify your identity') }); + setTryBiometricsCheck(false); + setDisplay(false); + } catch (error) { + Alert.alert(_('Could not verify your identify'), error.message); + setTryBiometricsCheck(false); + } finally { + FingerprintScanner.release(); + } + }; + + void biometricsCheck(); + }, [display, tryBiometricsCheck]); + + useEffect(() => { + if (initialPromptDone) return; + if (!display) return; + + const complete = (enableBiometrics: boolean) => { + setInitialPromptDone(true); + Setting.setValue('security.biometricsInitialPromptDone', true); + Setting.setValue('security.biometricsEnabled', enableBiometrics); + if (!enableBiometrics) { + setDisplay(false); + setTryBiometricsCheck(false); + } else { + setTryBiometricsCheck(true); + } + }; + + Alert.alert( + _('Enable biometrics authentication?'), + _('Use your biometrics to secure access to your application. You can always set it up later in Settings.'), + [ + { + text: _('Enable'), + onPress: () => complete(true), + style: 'default', + }, + { + text: _('Not now'), + onPress: () => complete(false), + style: 'cancel', + }, + ] + ); + }, [initialPromptDone, props.sensorInfo.supportedSensors, display]); + + const windowSize = useMemo(() => { + return { + width: Dimensions.get('window').width, + height: Dimensions.get('window').height, + }; + }, []); + + const renderTryAgainButton = () => { + if (!display || tryBiometricsCheck || !initialPromptDone) return null; + return