Commit 4aafcc3c by Nguyễn Thị Thúy

login by biometry

parent e1170e14
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* InvestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InvestTests.m; sourceTree = "<group>"; }; 00E356F21AD99517003FC87E /* InvestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InvestTests.m; sourceTree = "<group>"; };
12715EC58B6699B513B54F09 /* Pods-Invest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Invest.debug.xcconfig"; path = "Target Support Files/Pods-Invest/Pods-Invest.debug.xcconfig"; sourceTree = "<group>"; }; 12715EC58B6699B513B54F09 /* Pods-Invest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Invest.debug.xcconfig"; path = "Target Support Files/Pods-Invest/Pods-Invest.debug.xcconfig"; sourceTree = "<group>"; };
133EF4C3267736DE00366B03 /* InvestDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = InvestDebug.entitlements; path = Invest/InvestDebug.entitlements; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* Invest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Invest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* Invest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Invest.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Invest/AppDelegate.h; sourceTree = "<group>"; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Invest/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Invest/AppDelegate.m; sourceTree = "<group>"; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Invest/AppDelegate.m; sourceTree = "<group>"; };
...@@ -148,6 +149,7 @@ ...@@ -148,6 +149,7 @@
13B07FAE1A68108700A75B9A /* Invest */ = { 13B07FAE1A68108700A75B9A /* Invest */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
133EF4C3267736DE00366B03 /* InvestDebug.entitlements */,
52FB2B08262400D400DD7983 /* BootSplash.storyboard */, 52FB2B08262400D400DD7983 /* BootSplash.storyboard */,
9345F6C125FF213F006B5233 /* Fonts */, 9345F6C125FF213F006B5233 /* Fonts */,
52E1A15225F1255E00EA970D /* Invest.entitlements */, 52E1A15225F1255E00EA970D /* Invest.entitlements */,
...@@ -900,7 +902,7 @@ ...@@ -900,7 +902,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Invest/Invest.entitlements; CODE_SIGN_ENTITLEMENTS = Invest/InvestDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8; CURRENT_PROJECT_VERSION = 8;
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)dcv.investcustomer.vn</string>
</array>
</dict>
</plist>
...@@ -372,7 +372,7 @@ PODS: ...@@ -372,7 +372,7 @@ PODS:
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2) - React-jsi (= 0.62.2)
- ReactCommon/callinvoker (= 0.62.2) - ReactCommon/callinvoker (= 0.62.2)
- RNBootSplash (3.2.0): - RNBootSplash (3.2.3):
- React-Core - React-Core
- RNCAsyncStorage (1.12.1): - RNCAsyncStorage (1.12.1):
- React-Core - React-Core
...@@ -408,6 +408,8 @@ PODS: ...@@ -408,6 +408,8 @@ PODS:
- React-Core - React-Core
- React-RCTImage - React-RCTImage
- TOCropViewController - TOCropViewController
- RNKeychain (7.0.0):
- React-Core
- RNReanimated (1.13.2): - RNReanimated (1.13.2):
- React-Core - React-Core
- RNScreens (2.18.0): - RNScreens (2.18.0):
...@@ -421,6 +423,8 @@ PODS: ...@@ -421,6 +423,8 @@ PODS:
- libwebp (~> 1.0) - libwebp (~> 1.0)
- SDWebImage/Core (~> 5.7) - SDWebImage/Core (~> 5.7)
- TOCropViewController (2.6.0) - TOCropViewController (2.6.0)
- TouchID (4.4.1):
- React
- Yoga (1.14.0) - Yoga (1.14.0)
- YogaKit (1.18.1): - YogaKit (1.18.1):
- Yoga (~> 1.14) - Yoga (~> 1.14)
...@@ -490,9 +494,11 @@ DEPENDENCIES: ...@@ -490,9 +494,11 @@ DEPENDENCIES:
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNI18n (from `../node_modules/react-native-i18n`) - RNI18n (from `../node_modules/react-native-i18n`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNReanimated (from `../node_modules/react-native-reanimated`) - RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`) - RNScreens (from `../node_modules/react-native-screens`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`) - RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- TouchID (from `../node_modules/react-native-touch-id`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS: SPEC REPOS:
...@@ -611,12 +617,16 @@ EXTERNAL SOURCES: ...@@ -611,12 +617,16 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-i18n" :path: "../node_modules/react-native-i18n"
RNImageCropPicker: RNImageCropPicker:
:path: "../node_modules/react-native-image-crop-picker" :path: "../node_modules/react-native-image-crop-picker"
RNKeychain:
:path: "../node_modules/react-native-keychain"
RNReanimated: RNReanimated:
:path: "../node_modules/react-native-reanimated" :path: "../node_modules/react-native-reanimated"
RNScreens: RNScreens:
:path: "../node_modules/react-native-screens" :path: "../node_modules/react-native-screens"
RNVectorIcons: RNVectorIcons:
:path: "../node_modules/react-native-vector-icons" :path: "../node_modules/react-native-vector-icons"
TouchID:
:path: "../node_modules/react-native-touch-id"
Yoga: Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga" :path: "../node_modules/react-native/ReactCommon/yoga"
...@@ -675,7 +685,7 @@ SPEC CHECKSUMS: ...@@ -675,7 +685,7 @@ SPEC CHECKSUMS:
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256 React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3 ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
RNBootSplash: 24175aa28fe203b10c48dc34e78d946fd33c77af RNBootSplash: 8ef5ffa03dadd35f66510b42960ce40f397c98bf
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398 RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
RNCCheckbox: d1749e6a92178ce5dbc31e63becd1f34f0c76bbd RNCCheckbox: d1749e6a92178ce5dbc31e63becd1f34f0c76bbd
RNCClipboard: 245417a78ab585e0d4d83926c28907e7b2bc24bd RNCClipboard: 245417a78ab585e0d4d83926c28907e7b2bc24bd
...@@ -687,16 +697,18 @@ SPEC CHECKSUMS: ...@@ -687,16 +697,18 @@ SPEC CHECKSUMS:
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNI18n: e2f7e76389fcc6e84f2c8733ea89b92502351fd8 RNI18n: e2f7e76389fcc6e84f2c8733ea89b92502351fd8
RNImageCropPicker: 35a3ceb837446fa11547704709bb22b5fac6d584 RNImageCropPicker: 35a3ceb837446fa11547704709bb22b5fac6d584
RNKeychain: f75b8c8b2f17d3b2aa1f25b4a0ac5b83d947ff8f
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: f0d7a2a440a8ba9f4574ca1ddb3368f473891be4 RNScreens: f0d7a2a440a8ba9f4574ca1ddb3368f473891be4
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4 RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21 SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
TOCropViewController: 3105367e808b7d3d886a74ff59bf4804e7d3ab38 TOCropViewController: 3105367e808b7d3d886a74ff59bf4804e7d3ab38
TouchID: ba4c656d849cceabc2e4eef722dea5e55959ecf4
Yoga: 3ebccbdd559724312790e7742142d062476b698e Yoga: 3ebccbdd559724312790e7742142d062476b698e
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
YoutubePlayer-in-WKWebView: cfbf46da51d7370662a695a8f351e5fa1d3e1008 YoutubePlayer-in-WKWebView: cfbf46da51d7370662a695a8f351e5fa1d3e1008
PODFILE CHECKSUM: f6cddf7564cb78360d1490a138d2ad23d4135637 PODFILE CHECKSUM: d5f4bf13be9a761040ff8b01e612e6d56a04f1d5
COCOAPODS: 1.10.1 COCOAPODS: 1.10.1
...@@ -8588,11 +8588,6 @@ ...@@ -8588,11 +8588,6 @@
"resolved": "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz", "resolved": "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz",
"integrity": "sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg==" "integrity": "sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg=="
}, },
"react-native-local-auth": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/react-native-local-auth/-/react-native-local-auth-1.6.0.tgz",
"integrity": "sha512-36cYGZGCG82pMiVJbQa5WMA93khP4v5JqLutFkMyB/eRpCULHmojNIBlbUPIY9SCeN4sg5VBRFTVGCtTg2r2kA=="
},
"react-native-modal": { "react-native-modal": {
"version": "11.7.0", "version": "11.7.0",
"resolved": "https://registry.yarnpkg.com/react-native-modal/-/react-native-modal-11.7.0.tgz", "resolved": "https://registry.yarnpkg.com/react-native-modal/-/react-native-modal-11.7.0.tgz",
...@@ -8628,6 +8623,11 @@ ...@@ -8628,6 +8623,11 @@
"resolved": "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.18.0.tgz", "resolved": "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.18.0.tgz",
"integrity": "sha512-8+lCEsxzSu55GWRw6yZpyt3OszxN1OngfBsFXdqspaEfq6uIChanzlcD2PLVQl+iN82GAcrZM800Kd1pA477ZQ==" "integrity": "sha512-8+lCEsxzSu55GWRw6yZpyt3OszxN1OngfBsFXdqspaEfq6uIChanzlcD2PLVQl+iN82GAcrZM800Kd1pA477ZQ=="
}, },
"react-native-segmented-control-tab": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/react-native-segmented-control-tab/-/react-native-segmented-control-tab-3.4.1.tgz",
"integrity": "sha512-BNPdlE9Unr0Xabewn8W+FhBMLjssXy9Ey7S7AY0hXlrKrEKFdC9z0yT+eEWd5dLam4T6T4IuGL8b7ZF4uGyWNw=="
},
"react-native-simple-radio-button": { "react-native-simple-radio-button": {
"version": "2.7.4", "version": "2.7.4",
"resolved": "https://registry.npmjs.org/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.4.tgz", "resolved": "https://registry.npmjs.org/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.4.tgz",
......
...@@ -17,6 +17,7 @@ import R from '../assets/R'; ...@@ -17,6 +17,7 @@ import R from '../assets/R';
import {isTablet} from 'react-native-device-info'; import {isTablet} from 'react-native-device-info';
import {RSA_KEY, MY_RSA_KEY} from './constants'; import {RSA_KEY, MY_RSA_KEY} from './constants';
import JSEncrypt from 'jsencrypt'; import JSEncrypt from 'jsencrypt';
import KEY from '../assets/AsynStorage';
export const encryptRSAString = (val) => { export const encryptRSAString = (val) => {
var encrypt = new JSEncrypt(); var encrypt = new JSEncrypt();
...@@ -33,7 +34,7 @@ export const decryptRSAString = (val) => { ...@@ -33,7 +34,7 @@ export const decryptRSAString = (val) => {
}; };
export const logout = (navigation) => { export const logout = (navigation) => {
AsyncStorage.clear(); AsyncStorage.multiRemove([KEY.ACCOUNT, KEY.FIREBASE, KEY.TOKEN])
navigation.reset({ navigation.reset({
index: 1, index: 1,
routes: [{name: AUTHEN}], routes: [{name: AUTHEN}],
......
...@@ -33,12 +33,16 @@ import TouchID from 'react-native-touch-id'; ...@@ -33,12 +33,16 @@ import TouchID from 'react-native-touch-id';
import {call} from 'redux-saga/effects'; import {call} from 'redux-saga/effects';
import KeychainService from '../../services/keychainService'; import KeychainService from '../../services/keychainService';
import * as Keychain from 'react-native-keychain'; import * as Keychain from 'react-native-keychain';
import EntypoIcon from 'react-native-vector-icons/Entypo';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
const Login = (props) => { const Login = (props) => {
const {navigation} = props; const {navigation} = props;
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [pass, setPass] = useState(''); const [pass, setPass] = useState('');
const [biometryType, setBiometryType] = useState(null);
const [isShowBiometryLogin, setIsShowBiometryLogin] = useState(false);
const navigate = useNavigation(); const navigate = useNavigation();
const optionalConfigObject = { const optionalConfigObject = {
...@@ -56,11 +60,21 @@ const Login = (props) => { ...@@ -56,11 +60,21 @@ const Login = (props) => {
} }
} }
}; };
const getLoginByBiometry = async () => {
let loginByBiometry = await AsyncStorage.getItem(KEY.IS_LOGIN_BY_BIOMETRY);
if (loginByBiometry) {
loginByBiometry = JSON.parse(loginByBiometry)
setIsShowBiometryLogin(loginByBiometry.isLoginByBiometry);
loginByBiometry.isLoginByBiometry && Keychain.getSupportedBiometryType({}).then((biometryType) => {
setBiometryType(biometryType);
});
}
};
useEffect(() => { useEffect(() => {
props.hideLoading(); props.hideLoading();
getAccount(); getAccount();
getTokenDevice(); getTokenDevice();
getLoginByBiometry();
}, []); }, []);
const getAccount = async () => { const getAccount = async () => {
...@@ -68,19 +82,30 @@ const Login = (props) => { ...@@ -68,19 +82,30 @@ const Login = (props) => {
const account = JSON.parse(jsonValue); const account = JSON.parse(jsonValue);
if (account) { if (account) {
onSubmitLogin(account.email, account.pass); onSubmitLogin(account.email, account.pass);
} else {
getCredentialInfo()
} }
}; };
const getCredentialInfo = async () => { const getCredentialInfo = async () => {
try { try {
// Retrieve the credentials // Retrieve the credentials
const credentials = await Keychain.getGenericPassword(); const options = {
authenticationPrompt: {
title: 'Authentication needed',
cancel: 'Cancel',
},
};
const credentials = await Keychain.getGenericPassword(options);
if (credentials) { if (credentials) {
console.log( console.log(
'Credentials successfully loaded for user ', credentials, 'Credentials successfully loaded for user ', credentials,
); );
onSubmitLogin(credentials.username, credentials.password);
} else { } else {
showAlert(
TYPE.ERROR,
I18n.t('Notification'),
I18n.t('HaveNotCredential', {type: biometryType == 'FaceID' ? I18n.t('FaceId') : I18n.t('Fingerprint')}),
);
console.log('No credentials stored'); console.log('No credentials stored');
} }
} catch (error) { } catch (error) {
...@@ -115,7 +140,6 @@ const Login = (props) => { ...@@ -115,7 +140,6 @@ const Login = (props) => {
index: 1, index: 1,
routes: [{name: TABNAVIGATOR}], routes: [{name: TABNAVIGATOR}],
}); });
await Keychain.setGenericPassword(email, pass);
} else { } else {
showAlert(TYPE.ERROR, I18n.t('Notification'), res.data.message); showAlert(TYPE.ERROR, I18n.t('Notification'), res.data.message);
} }
...@@ -159,7 +183,7 @@ const Login = (props) => { ...@@ -159,7 +183,7 @@ const Login = (props) => {
<TouchableOpacity <TouchableOpacity
onPress={() => navigate.navigate(CONFIRMEMAIL)} onPress={() => navigate.navigate(CONFIRMEMAIL)}
style={styles.forgotView}> style={styles.forgotView}>
<AppText i18nKey={'ForgotPassword'} style={styles.txtTitle} /> <AppText i18nKey={'ForgotPassword'} style={styles.txtTitle}/>
</TouchableOpacity> </TouchableOpacity>
<View <View
...@@ -170,21 +194,41 @@ const Login = (props) => { ...@@ -170,21 +194,41 @@ const Login = (props) => {
<TouchableOpacity <TouchableOpacity
onPress={() => onSubmitLogin(email, pass)} onPress={() => onSubmitLogin(email, pass)}
style={styles.wrapLogin}> style={styles.wrapLogin}>
<AppText i18nKey={'Login'} style={styles.txtLogin} /> <AppText i18nKey={'Login'} style={styles.txtLogin}/>
<Image source={R.images.iconRight1} style={styles.imgIcon} /> <Image source={R.images.iconRight1} style={styles.imgIcon}/>
</TouchableOpacity> </TouchableOpacity>
{isShowBiometryLogin ?
<View style={{flexDirection: 'row', marginTop: WIDTHXD(70)}}>
{biometryType == 'FaceID' ?
<TouchableOpacity
onPress={() => {
getCredentialInfo();
}}>
<Image source={R.images.iconFaceId} style={[styles.imgIconBiometry, {tintColor: R.colors.main}]}/>
</TouchableOpacity>
:
<TouchableOpacity
onPress={() => {
getCredentialInfo();
}}>
<Image source={R.images.fingerprint} style={styles.imgIconBiometry}/>
</TouchableOpacity>
}
</View>
: null}
<View style={styles.row}> <View style={styles.row}>
<AppText i18nKey={'Have_account'} style={styles.txtTitle} /> <AppText i18nKey={'Have_account'} style={styles.txtTitle}/>
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
navigation.navigate('REGISTOR'); navigation.navigate('REGISTOR');
}}> }}>
<AppText i18nKey={'Register'} style={styles.txtRegistor} /> <AppText i18nKey={'Register'} style={styles.txtRegistor}/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
<View style={{height: 100}} /> <View style={{height: 100}}/>
</View> </View>
); );
}; };
...@@ -217,6 +261,10 @@ const styles = StyleSheet.create({ ...@@ -217,6 +261,10 @@ const styles = StyleSheet.create({
height: HEIGHTXD(72), height: HEIGHTXD(72),
marginLeft: 5, marginLeft: 5,
}, },
imgIconBiometry: {
width: WIDTHXD(120),
height: WIDTHXD(120),
},
row: { row: {
flexDirection: 'row', flexDirection: 'row',
marginTop: 30, marginTop: 30,
......
import React, {useState, useEffect, useRef} from 'react';
import {Modal, View, TouchableWithoutFeedback, TouchableOpacity, StyleSheet, Text, TextInput} from 'react-native';
import IconClose from 'react-native-vector-icons/AntDesign';
import R from '../../assets/R';
import {WIDTHXD, getWidth, HEIGHTXD, getFontXD, getHeight} from '../../Config/Functions';
import {showAlert, TYPE} from '../../components/DropdownAlert';
import I18n from '../../helper/i18/i18n';
import DropdownAlert from 'react-native-dropdownalert';
const EnterPasswordModal = (props) => {
const [visible, setVisible] = useState(true);
const [pass, setPass] = useState('');
const dropDownAlertRef = useRef(null);
const onChangeText = (text) => {
setPass(text);
};
useEffect(() => {
setVisible(props.visible);
}, [props.visible]);
return <Modal
animationType='slide'
transparent={true}
visible={visible}
onRequestClose={() => {
props.setVisible(false);
setPass('');
}}
>
<TouchableWithoutFeedback
onPress={() => {
setPass('');
props.setVisible(false);
}}
>
<View
style={styles.opacity}
>
<View style={styles.modal}>
<View style={styles.viewTitle}>
<View style={styles.viewEmpty}></View>
<Text style={styles.titlePopup}>{I18n.t('EnterPassword')}</Text>
<TouchableOpacity onPress={() => {
setPass('');
props.setVisible(false);
}} style={styles.btClose}>
<IconClose name='close' size={WIDTHXD(48)} color={R.colors.black}/>
</TouchableOpacity>
</View>
<TextInput
autoCapitalize="none"
onChangeText={(val) => onChangeText(val)}
style={styles.txtInput}
placeholderTextColor={R.colors.placeHolder}
secureTextEntry={true}
autoFocus={true}
value={pass}
/>
<TouchableOpacity onPress={() => {
if (pass == '') {
dropDownAlertRef.current.alertWithType(TYPE.WARN, I18n.t('Notification'), `${I18n.t('Please_fill_in')}${I18n.t('Password')}`);
} else {
props.accept(pass);
setPass('')
}
}}>
<Text style={styles.txtAccept}>{I18n.t('Ok')}</Text>
</TouchableOpacity>
</View>
<DropdownAlert
inactiveStatusBarBackgroundColor={R.colors.main}
activeStatusBarBackgroundColor={R.colors.main}
warnImageSrc={R.images.iconWarn}
successImageSrc={R.images.iconSuccess}
errorImageSrc={R.images.iconError}
titleStyle={{color: '#fff'}}
messageStyle={{color: '#fff'}}
updateStatusBar={false}
closeInterval={1000}
ref={dropDownAlertRef}
warnColor={R.colors.orange400}
defaultContainer={{
borderBottomRightRadius: WIDTHXD(30),
borderBottomLeftRadius: WIDTHXD(30),
paddingTop: HEIGHTXD(30),
paddingVertical: HEIGHTXD(30),
paddingHorizontal: WIDTHXD(20),
}}
/>
</View>
</TouchableWithoutFeedback>
</Modal>;
};
const styles = StyleSheet.create({
opacity: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#rgba(0,0,0,0.7)',
},
titlePopup: {
fontSize: getFontXD(48),
color: R.colors.black,
textAlign: 'center',
flex: 10,
},
viewEmpty: {
flex: 1,
},
viewTitle: {
flexDirection: 'row',
width: WIDTHXD(960),
borderBottomWidth: 0.3,
paddingBottom: HEIGHTXD(50),
borderBottomColor: R.colors.iconGray,
borderColor: R.colors.iconGray,
},
txtAccept: {
fontSize: getFontXD(48),
color: R.colors.main,
textAlign: 'center',
marginTop: HEIGHTXD(50),
},
btClose: {
flex: 1,
justifyContent: 'center',
alignItems: 'flex-start',
},
modal: {
backgroundColor: R.colors.white,
width: WIDTHXD(960),
justifyContent: 'center',
// maxHeight: HEIGHTXD(1300),
borderRadius: WIDTHXD(20),
paddingBottom: WIDTHXD(40),
// minHeight: HEIGHTXD(369),
paddingVertical: HEIGHTXD(59),
paddingTop: HEIGHTXD(61),
paddingHorizontal: WIDTHXD(60),
alignItems: 'center',
},
txtInput: {
width: WIDTHXD(880),
height: HEIGHTXD(109),
marginTop: HEIGHTXD(50),
color: 'black',
borderRadius: 7,
borderWidth: 0.7,
borderColor: '#DBDBDB',
fontSize: getFontXD(42),
paddingVertical: 5,
paddingHorizontal: 10,
backgroundColor: 'white',
shadowColor: '#AFA9A9',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.25,
shadowRadius: 1.84,
elevation: 1,
},
});
export default EnterPasswordModal;
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import {View, Text, Switch, StyleSheet} from 'react-native'; import {View, Text, Switch, StyleSheet, Platform} from 'react-native';
import HeaderBack from '../../components/Header/HeaderBack'; import HeaderBack from '../../components/Header/HeaderBack';
import {getFontXD} from '../../Config/Functions'; import {encryptRSAString, getFontXD} from '../../Config/Functions';
import PickerItem from '../../components/Picker/PickerItem'; import PickerItem from '../../components/Picker/PickerItem';
import AppText from '../../components/AppText'; import AppText from '../../components/AppText';
import {changeLanguage} from '../../actions/language'; import {changeLanguage} from '../../actions/language';
...@@ -9,6 +9,12 @@ import {connect} from 'react-redux'; ...@@ -9,6 +9,12 @@ import {connect} from 'react-redux';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import KEY from '../../assets/AsynStorage'; import KEY from '../../assets/AsynStorage';
import I18n, {setLocation} from '../../helper/i18/i18n'; import I18n, {setLocation} from '../../helper/i18/i18n';
import EnterPasswordModal from './EnterPasswordModal';
import * as Keychain from 'react-native-keychain';
import {showLoading, hideLoading} from '../../actions/loadingAction';
import {verifyPassword} from '../../apis/Functions/users';
import {showAlert, TYPE} from '../../components/DropdownAlert';
const dataLanguage = [ const dataLanguage = [
{ {
value: 'vi', value: 'vi',
...@@ -21,37 +27,72 @@ const dataLanguage = [ ...@@ -21,37 +27,72 @@ const dataLanguage = [
]; ];
const SettingView = (props) => { const SettingView = (props) => {
const [isEnabled, setIsEnabled] = useState(true); const [isEnabled, setIsEnabled] = useState(false);
const toggleSwitch = () => setIsEnabled((previousState) => !previousState); const [visible, setVisible] = useState(false);
const [biometryType, setBiometryType] = useState(null);
const toggleSwitch = async () => {
if (isEnabled == true) {
await Keychain.resetGenericPassword();
AsyncStorage.setItem(KEY.IS_LOGIN_BY_BIOMETRY, JSON.stringify({isLoginByBiometry : false}));
setIsEnabled(false);
} else {
setVisible(true);
}
};
const [language, setLanguage] = useState(); const [language, setLanguage] = useState();
useEffect(() => { useEffect(() => {
convertLanguage(); convertLanguage();
getLoginByBiometry()
Keychain.getSupportedBiometryType({}).then((biometryType) => {
setBiometryType(biometryType);
});
}, []); }, []);
const getLoginByBiometry = async () => {
let loginByBiometry = await AsyncStorage.getItem(KEY.IS_LOGIN_BY_BIOMETRY);
if (loginByBiometry) {
setIsEnabled(JSON.parse(loginByBiometry).isLoginByBiometry);
}
};
const savePass = async (pass) => {
setVisible(false);
props.showLoading();
console.log(props.user);
const res = await verifyPassword({
password: encryptRSAString(pass),
platform: Platform.OS,
account_type: 'CUSTOMER',
});
if (res.status == 200 && res.data) {
if (res.data.code == 200) {
await Keychain.setGenericPassword(props.user.email, pass, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
});
AsyncStorage.setItem(KEY.IS_LOGIN_BY_BIOMETRY, JSON.stringify({isLoginByBiometry : true}));
setIsEnabled(true);
} else {
showAlert(TYPE.ERROR, I18n.t('Notification'), res.data.message);
}
} else {
showAlert(TYPE.ERROR, I18n.t('Notification'), I18n.t('HaveIssue'));
}
props.hideLoading();
};
const convertLanguage = () => { const convertLanguage = () => {
const temp = dataLanguage.filter((e) => e.value == props.language.language); const temp = dataLanguage.filter((e) => e.value == props.language.language);
setLanguage(temp[0].name); setLanguage(temp[0].name);
}; };
return ( return (
<>
<View style={{flex: 1}}> <View style={{flex: 1}}>
<HeaderBack title={'Setting'} /> <HeaderBack title={'Setting'}/>
<View style={{flex: 1, padding: 10}}> <View style={{flex: 1, padding: 10}}>
{/* <View style={styles.row}>
<Text style={styles.txtTitle}>Bật thông báo</Text>
<Switch
trackColor={{false: '#DBDBDB', true: '#1C6AF6'}}
ios_backgroundColor="#767577"
thumbColor={'#f4f3f4'}
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View> */}
<View style={styles.row}> <View style={styles.row}>
<AppText i18nKey={'Language'} style={styles.txtTitle} /> <AppText i18nKey={'Language'} style={styles.txtTitle}/>
<PickerItem <PickerItem
width={200} width={200}
defaultValue={language} defaultValue={language}
...@@ -66,8 +107,26 @@ const SettingView = (props) => { ...@@ -66,8 +107,26 @@ const SettingView = (props) => {
}} }}
/> />
</View> </View>
<View style={styles.row}>
<Text style={styles.txtTitle}>{I18n.t('LoginBy', {type: biometryType =='FaceID' ? I18n.t('FaceId') : I18n.t('Fingerprint')})}</Text>
<Switch
trackColor={{false: '#DBDBDB', true: '#1C6AF6'}}
ios_backgroundColor="#767577"
thumbColor={'#f4f3f4'}
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View>
</View> </View>
<EnterPasswordModal
visible={visible}
accept={(pass) => savePass(pass)}
setVisible={(visible) => setVisible(visible)}
/>
</View> </View>
</>
); );
}; };
...@@ -87,8 +146,9 @@ const styles = StyleSheet.create({ ...@@ -87,8 +146,9 @@ const styles = StyleSheet.create({
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
user: state.userReducer,
language: state.languageReducer, language: state.languageReducer,
}; };
}; };
export default connect(mapStateToProps, {changeLanguage})(SettingView); export default connect(mapStateToProps, {changeLanguage, showLoading, hideLoading})(SettingView);
...@@ -91,3 +91,8 @@ export const updateOTPApiSmart = async (body) => ...@@ -91,3 +91,8 @@ export const updateOTPApiSmart = async (body) =>
PostData(url.urlUpdateSmartOTP, body) PostData(url.urlUpdateSmartOTP, body)
.then((res) => res) .then((res) => res)
.catch((err) => err); .catch((err) => err);
export const verifyPassword = async (body) =>
PostData(url.urlVerifyPassword, body)
.then((res) => res)
.catch((err) => err);
...@@ -68,4 +68,5 @@ export default { ...@@ -68,4 +68,5 @@ export default {
urlGetSmartOTP: root + 'api/v2/customer-get-otp', urlGetSmartOTP: root + 'api/v2/customer-get-otp',
urlStoreOTPSmart: root + 'api/v1/customers/store-otp-password', urlStoreOTPSmart: root + 'api/v1/customers/store-otp-password',
urlVerufySmartOTP: root + 'api/v1/customers/verify-otp-password', urlVerufySmartOTP: root + 'api/v1/customers/verify-otp-password',
urlVerifyPassword: `${root}api/auth/customer-verify-password`,
}; };
...@@ -3,6 +3,7 @@ const KEY = { ...@@ -3,6 +3,7 @@ const KEY = {
FIREBASE: '@Firebase', FIREBASE: '@Firebase',
ACCOUNT: '@ACCOUNT', ACCOUNT: '@ACCOUNT',
LANGUAGE: '@LANGUAGE', LANGUAGE: '@LANGUAGE',
IS_LOGIN_BY_BIOMETRY: '@IS_LOGIN_BY_BIOMETRY',
}; };
export default KEY; export default KEY;
...@@ -118,6 +118,8 @@ const images = { ...@@ -118,6 +118,8 @@ const images = {
rules: require('./images/rules.png'), rules: require('./images/rules.png'),
changeSmart: require('./images/changeSmart.png'), changeSmart: require('./images/changeSmart.png'),
faq: require('./images/faq.png'), faq: require('./images/faq.png'),
fingerprint: require('./images/fingerprint.png'),
iconFaceId: require('./images/iconFaceID.png'),
}; };
export default images; export default images;
...@@ -328,4 +328,10 @@ export default { ...@@ -328,4 +328,10 @@ export default {
WarnMaxReqestWithdraw: 'Invalid withdrawal amount', WarnMaxReqestWithdraw: 'Invalid withdrawal amount',
YouHaveNotSettingSmartOTP: 'You have not installed Smart OTP', YouHaveNotSettingSmartOTP: 'You have not installed Smart OTP',
OTP: 'Enter OTP', OTP: 'Enter OTP',
HaveIssue: 'Have an issue, try again!',
HaveNotCredential: 'Can not login by %{type}, please return on login by %{type}',
LoginBy: 'Login by %{type}',
Fingerprint: 'Fingerprint',
FaceId: 'FaceId'
}; };
...@@ -325,4 +325,9 @@ export default { ...@@ -325,4 +325,9 @@ export default {
ForgotSmartOTP: 'Quên Smart OTP', ForgotSmartOTP: 'Quên Smart OTP',
YouHaveNotSettingSmartOTP: 'Bạn chưa cài đặt Smart OTP', YouHaveNotSettingSmartOTP: 'Bạn chưa cài đặt Smart OTP',
OTP: 'Nhập OTP', OTP: 'Nhập OTP',
HaveIssue: 'Có lỗi xảy ra, vui lòng thử lại',
HaveNotCredential: 'Không thể đăng nhập được bằng %{type}, vui lòng bật lại chức năng đăng nhập bằng %{type}',
LoginBy: 'Đăng nhập bằng %{type}',
Fingerprint: 'vân tay',
FaceId: 'nhận diện khuôn mặt'
}; };
...@@ -6949,6 +6949,11 @@ react-native-screens@^2.17.1: ...@@ -6949,6 +6949,11 @@ react-native-screens@^2.17.1:
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.18.0.tgz" resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.18.0.tgz"
integrity sha512-8+lCEsxzSu55GWRw6yZpyt3OszxN1OngfBsFXdqspaEfq6uIChanzlcD2PLVQl+iN82GAcrZM800Kd1pA477ZQ== integrity sha512-8+lCEsxzSu55GWRw6yZpyt3OszxN1OngfBsFXdqspaEfq6uIChanzlcD2PLVQl+iN82GAcrZM800Kd1pA477ZQ==
react-native-segmented-control-tab@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/react-native-segmented-control-tab/-/react-native-segmented-control-tab-3.4.1.tgz#b6e54b8975ce8092315c9b0a1ab58b834d8ccf8e"
integrity sha512-BNPdlE9Unr0Xabewn8W+FhBMLjssXy9Ey7S7AY0hXlrKrEKFdC9z0yT+eEWd5dLam4T6T4IuGL8b7ZF4uGyWNw==
react-native-simple-radio-button@^2.7.4: react-native-simple-radio-button@^2.7.4:
version "2.7.4" version "2.7.4"
resolved "https://registry.yarnpkg.com/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.4.tgz#86e2dbe8af9e6bf60eee088f60466f7a975e7758" resolved "https://registry.yarnpkg.com/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.4.tgz#86e2dbe8af9e6bf60eee088f60466f7a975e7758"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment