Commit b6cd9aeb by Nguyễn Thị Thúy

Merge branch 'func_fingerprint_login' into 'func_smart_otp'

Func fingerprint login

See merge request !19
parents c5d2122a 130951cc
......@@ -57,3 +57,5 @@ buck-out/
# CocoaPods
/ios/Pods/
/android/app/release/app-release.aab
/ios/Podfile.lock
{
"version": 1,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.dcv.invest",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"properties": [],
"versionCode": 16,
"versionName": "2.0",
"enabled": true,
"outputFile": "app-release.apk"
}
]
}
\ No newline at end of file
......@@ -2,24 +2,27 @@
package="com.dcv.invest">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
>
android:usesCleartextTraffic="true">
<!-- <meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/bootsplash_background" /> -->
......@@ -27,24 +30,28 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="appdcvinvest" android:host="screen" android:pathPrefix="/"
/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="screen"
android:pathPrefix="/"
android:scheme="appdcvinvest" />
</intent-filter>
</activity>
<activity
android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
android:theme="@style/BootTheme"
android:launchMode="singleTask">
android:launchMode="singleTask"
android:theme="@style/BootTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
......@@ -52,8 +59,6 @@
</intent-filter>
</activity>
......
......@@ -4,6 +4,8 @@ import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.rnfingerprint.FingerprintAuthPackage;
import com.rnfingerprint.FingerprintAuthPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.reactnativecommunity.netinfo.NetInfoPackage;
import com.facebook.react.ReactInstanceManager;
......
......@@ -4,7 +4,7 @@ buildscript {
ext {
buildToolsVersion = "29.0.2"
minSdkVersion = 21
compileSdkVersion = 28
compileSdkVersion = 29
targetSdkVersion = 29
}
repositories {
......
rootProject.name = 'Invest'
include ':react-native-touch-id'
project(':react-native-touch-id').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-touch-id/android')
include ':react-native-device-info'
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
include ':@react-native-community_netinfo'
......
......@@ -49,6 +49,7 @@
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>"; };
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; };
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>"; };
......@@ -148,6 +149,7 @@
13B07FAE1A68108700A75B9A /* Invest */ = {
isa = PBXGroup;
children = (
133EF4C3267736DE00366B03 /* InvestDebug.entitlements */,
52FB2B08262400D400DD7983 /* BootSplash.storyboard */,
9345F6C125FF213F006B5233 /* Fonts */,
52E1A15225F1255E00EA970D /* Invest.entitlements */,
......@@ -900,7 +902,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Invest/Invest.entitlements;
CODE_SIGN_ENTITLEMENTS = Invest/InvestDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8;
......
......@@ -63,7 +63,7 @@
<key>NSCameraUsageDescription</key>
<string>Accect connect camera</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<string/>
<key>NSPhotoLibraryUsageDescription</key>
<string>To upload images</string>
<key>UIAppFonts</key>
......@@ -102,5 +102,7 @@
<false/>
<key>NSFaceIDUsageDescription</key>
<string>Enabling Face ID allows you quick and secure access to your account.</string>
<key>NSFaceIDUsageDescription</key>
<string>Enabling Face ID allows you quick and secure access to your account.</string>
</dict>
</plist>
<?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>
......@@ -91,7 +91,7 @@ target 'Invest' do
pod 'RNDeviceInfo', :path => '../node_modules/react-native-device-info'
pod 'TouchID', :path => '../node_modules/react-native-touch-id'
target 'InvestTests' do
inherit! :complete
......
......@@ -372,7 +372,7 @@ PODS:
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- ReactCommon/callinvoker (= 0.62.2)
- RNBootSplash (3.2.0):
- RNBootSplash (3.2.3):
- React-Core
- RNCAsyncStorage (1.12.1):
- React-Core
......@@ -408,6 +408,8 @@ PODS:
- React-Core
- React-RCTImage
- TOCropViewController
- RNKeychain (7.0.0):
- React-Core
- RNReanimated (1.13.2):
- React-Core
- RNScreens (2.18.0):
......@@ -421,6 +423,8 @@ PODS:
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.7)
- TOCropViewController (2.6.0)
- TouchID (4.4.1):
- React
- Yoga (1.14.0)
- YogaKit (1.18.1):
- Yoga (~> 1.14)
......@@ -490,9 +494,11 @@ DEPENDENCIES:
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNI18n (from `../node_modules/react-native-i18n`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- TouchID (from `../node_modules/react-native-touch-id`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
......@@ -611,12 +617,16 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-i18n"
RNImageCropPicker:
:path: "../node_modules/react-native-image-crop-picker"
RNKeychain:
:path: "../node_modules/react-native-keychain"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNVectorIcons:
:path: "../node_modules/react-native-vector-icons"
TouchID:
:path: "../node_modules/react-native-touch-id"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
......@@ -675,7 +685,7 @@ SPEC CHECKSUMS:
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
RNBootSplash: 24175aa28fe203b10c48dc34e78d946fd33c77af
RNBootSplash: 8ef5ffa03dadd35f66510b42960ce40f397c98bf
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
RNCCheckbox: d1749e6a92178ce5dbc31e63becd1f34f0c76bbd
RNCClipboard: 245417a78ab585e0d4d83926c28907e7b2bc24bd
......@@ -687,16 +697,18 @@ SPEC CHECKSUMS:
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNI18n: e2f7e76389fcc6e84f2c8733ea89b92502351fd8
RNImageCropPicker: 35a3ceb837446fa11547704709bb22b5fac6d584
RNKeychain: f75b8c8b2f17d3b2aa1f25b4a0ac5b83d947ff8f
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: f0d7a2a440a8ba9f4574ca1ddb3368f473891be4
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
TOCropViewController: 3105367e808b7d3d886a74ff59bf4804e7d3ab38
TouchID: ba4c656d849cceabc2e4eef722dea5e55959ecf4
Yoga: 3ebccbdd559724312790e7742142d062476b698e
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
YoutubePlayer-in-WKWebView: cfbf46da51d7370662a695a8f351e5fa1d3e1008
PODFILE CHECKSUM: f6cddf7564cb78360d1490a138d2ad23d4135637
PODFILE CHECKSUM: d5f4bf13be9a761040ff8b01e612e6d56a04f1d5
COCOAPODS: 1.10.1
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -31,7 +31,7 @@
"react-hook-form": "^6.15.4",
"react-native": "0.62.2",
"react-native-autocomplete-input": "^5.0.0",
"react-native-bootsplash": "^3.2.0",
"react-native-bootsplash": "^3.2.3",
"react-native-confirmation-code-field": "^6.5.1",
"react-native-datepicker": "^1.7.2",
"react-native-device-info": "^8.1.2",
......@@ -41,6 +41,7 @@
"react-native-i18n": "^2.0.15",
"react-native-image-crop-picker": "^0.36.2",
"react-native-indicators": "^0.17.0",
"react-native-keychain": "^7.0.0",
"react-native-linear-gradient": "^2.5.6",
"react-native-modal": "^11.7.0",
"react-native-modal-dropdown": "^1.0.0",
......@@ -49,9 +50,11 @@
"react-native-reanimated": "^1.13.2",
"react-native-safe-area-context": "^3.1.9",
"react-native-screens": "^2.17.1",
"react-native-segmented-control-tab": "^3.4.1",
"react-native-simple-radio-button": "^2.7.4",
"react-native-swiper": "^1.6.0",
"react-native-tab-view": "^2.15.2",
"react-native-touch-id": "^4.4.1",
"react-native-vector-icons": "^8.0.0",
"react-native-webview": "^11.2.5",
"react-native-youtube": "^2.0.1",
......
......@@ -17,6 +17,7 @@ import R from '../assets/R';
import {isTablet} from 'react-native-device-info';
import {RSA_KEY, MY_RSA_KEY} from './constants';
import JSEncrypt from 'jsencrypt';
import KEY from '../assets/AsynStorage';
export const encryptRSAString = (val) => {
var encrypt = new JSEncrypt();
......@@ -33,7 +34,7 @@ export const decryptRSAString = (val) => {
};
export const logout = (navigation) => {
AsyncStorage.clear();
AsyncStorage.multiRemove([KEY.ACCOUNT, KEY.FIREBASE, KEY.TOKEN])
navigation.reset({
index: 1,
routes: [{name: AUTHEN}],
......
......@@ -29,15 +29,27 @@ import messaging from '@react-native-firebase/messaging';
import I18n from '../../helper/i18/i18n';
import AppText from '../../components/AppText';
import {showAlert, TYPE} from '../../components/DropdownAlert';
import TouchID from 'react-native-touch-id';
import {call} from 'redux-saga/effects';
import KeychainService from '../../services/keychainService';
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 {navigation} = props;
const [email, setEmail] = useState('');
const [pass, setPass] = useState('');
const [biometryType, setBiometryType] = useState(null);
const [isShowBiometryLogin, setIsShowBiometryLogin] = useState(false);
const navigate = useNavigation();
const optionalConfigObject = {
title: 'Authentication Required', // Android
color: '#e00606', // Android,
fallbackLabel: '', // iOS (if empty, then label is hidden)
};
const getTokenDevice = async () => {
let fcmToken = await AsyncStorage.getItem(KEY.FIREBASE);
......@@ -48,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(() => {
props.hideLoading();
getAccount();
getTokenDevice();
getLoginByBiometry();
}, []);
const getAccount = async () => {
......@@ -62,7 +84,34 @@ const Login = (props) => {
onSubmitLogin(account.email, account.pass);
}
};
const getCredentialInfo = async () => {
try {
// Retrieve the credentials
const options = {
authenticationPrompt: {
title: 'Authentication needed',
cancel: 'Cancel',
},
};
const credentials = await Keychain.getGenericPassword(options);
if (credentials) {
console.log(
'Credentials successfully loaded for user ', credentials,
);
onSubmitLogin(credentials.username, credentials.password);
} else {
showAlert(
TYPE.ERROR,
I18n.t('Notification'),
I18n.t('HaveNotCredential', {type: biometryType == 'FaceID' ? I18n.t('FaceId') : I18n.t('Fingerprint')}),
);
console.log('No credentials stored');
}
} catch (error) {
console.log('Keychain couldn\'t be accessed!', error);
}
};
const onSubmitLogin = async (email, pass) => {
const titles = [
I18n.t('Username').toLowerCase(),
......@@ -134,7 +183,7 @@ const Login = (props) => {
<TouchableOpacity
onPress={() => navigate.navigate(CONFIRMEMAIL)}
style={styles.forgotView}>
<AppText i18nKey={'ForgotPassword'} style={styles.txtTitle} />
<AppText i18nKey={'ForgotPassword'} style={styles.txtTitle}/>
</TouchableOpacity>
<View
......@@ -145,21 +194,41 @@ const Login = (props) => {
<TouchableOpacity
onPress={() => onSubmitLogin(email, pass)}
style={styles.wrapLogin}>
<AppText i18nKey={'Login'} style={styles.txtLogin} />
<Image source={R.images.iconRight1} style={styles.imgIcon} />
<AppText i18nKey={'Login'} style={styles.txtLogin}/>
<Image source={R.images.iconRight1} style={styles.imgIcon}/>
</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}>
<AppText i18nKey={'Have_account'} style={styles.txtTitle} />
<AppText i18nKey={'Have_account'} style={styles.txtTitle}/>
<TouchableOpacity
onPress={() => {
navigation.navigate('REGISTOR');
}}>
<AppText i18nKey={'Register'} style={styles.txtRegistor} />
<AppText i18nKey={'Register'} style={styles.txtRegistor}/>
</TouchableOpacity>
</View>
</View>
<View style={{height: 100}} />
<View style={{height: 100}}/>
</View>
);
};
......@@ -192,6 +261,10 @@ const styles = StyleSheet.create({
height: HEIGHTXD(72),
marginLeft: 5,
},
imgIconBiometry: {
width: WIDTHXD(120),
height: WIDTHXD(120),
},
row: {
flexDirection: 'row',
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 {View, Text, Switch, StyleSheet} from 'react-native';
import {View, Text, Switch, StyleSheet, Platform} from 'react-native';
import HeaderBack from '../../components/Header/HeaderBack';
import {getFontXD} from '../../Config/Functions';
import {encryptRSAString, getFontXD} from '../../Config/Functions';
import PickerItem from '../../components/Picker/PickerItem';
import AppText from '../../components/AppText';
import {changeLanguage} from '../../actions/language';
......@@ -9,6 +9,12 @@ import {connect} from 'react-redux';
import AsyncStorage from '@react-native-community/async-storage';
import KEY from '../../assets/AsynStorage';
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 = [
{
value: 'vi',
......@@ -21,37 +27,72 @@ const dataLanguage = [
];
const SettingView = (props) => {
const [isEnabled, setIsEnabled] = useState(true);
const toggleSwitch = () => setIsEnabled((previousState) => !previousState);
const [isEnabled, setIsEnabled] = useState(false);
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();
useEffect(() => {
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 temp = dataLanguage.filter((e) => e.value == props.language.language);
setLanguage(temp[0].name);
};
return (
<>
<View style={{flex: 1}}>
<HeaderBack title={'Setting'} />
<HeaderBack title={'Setting'}/>
<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}>
<AppText i18nKey={'Language'} style={styles.txtTitle} />
<AppText i18nKey={'Language'} style={styles.txtTitle}/>
<PickerItem
width={200}
defaultValue={language}
......@@ -66,8 +107,26 @@ const SettingView = (props) => {
}}
/>
</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>
<EnterPasswordModal
visible={visible}
accept={(pass) => savePass(pass)}
setVisible={(visible) => setVisible(visible)}
/>
</View>
</>
);
};
......@@ -87,8 +146,9 @@ const styles = StyleSheet.create({
const mapStateToProps = (state) => {
return {
user: state.userReducer,
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) =>
PostData(url.urlUpdateSmartOTP, body)
.then((res) => res)
.catch((err) => err);
export const verifyPassword = async (body) =>
PostData(url.urlVerifyPassword, body)
.then((res) => res)
.catch((err) => err);
......@@ -68,4 +68,5 @@ export default {
urlGetSmartOTP: root + 'api/v2/customer-get-otp',
urlStoreOTPSmart: root + 'api/v1/customers/store-otp-password',
urlVerufySmartOTP: root + 'api/v1/customers/verify-otp-password',
urlVerifyPassword: `${root}api/auth/customer-verify-password`,
};
......@@ -3,6 +3,7 @@ const KEY = {
FIREBASE: '@Firebase',
ACCOUNT: '@ACCOUNT',
LANGUAGE: '@LANGUAGE',
IS_LOGIN_BY_BIOMETRY: '@IS_LOGIN_BY_BIOMETRY',
};
export default KEY;
......@@ -118,6 +118,8 @@ const images = {
rules: require('./images/rules.png'),
changeSmart: require('./images/changeSmart.png'),
faq: require('./images/faq.png'),
fingerprint: require('./images/fingerprint.png'),
iconFaceId: require('./images/iconFaceID.png'),
};
export default images;
......@@ -156,13 +156,13 @@ export default {
ChangePasswordSuccess: 'Change password success',
EnterAllInfo: 'Please complete all information ',
Date: 'Date',
ReviewService: 'Review service of DCV Invest',
ReviewService: 'Review services of DCV Invest',
VeryBad: 'Very bad',
Bad: 'Bad',
Normal: 'Normal',
Good: 'Good',
VeryGood: 'Very good',
ShareYourFeel: 'Share your feel about service',
ShareYourFeel: 'Share your feel about services',
UploadImage: 'Upload image',
BonusMoney: 'Bonus money',
Content: 'Content',
......@@ -328,4 +328,10 @@ export default {
WarnMaxReqestWithdraw: 'Invalid withdrawal amount',
YouHaveNotSettingSmartOTP: 'You have not installed Smart 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 {
ForgotSmartOTP: 'Quên Smart OTP',
YouHaveNotSettingSmartOTP: 'Bạn chưa cài đặt Smart 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'
};
import TouchID from 'react-native-touch-id';
import {Platform} from 'react-native'
export const checkBiometricSupportednEnrolled = async () => {
const optionalConfigObject = {
unifiedErrors: false, // use unified error messages (default false)
passcodeFallback: false // if true is passed, it will allow isSupported to return an error if the device is not enrolled in touch id/face id etc. Otherwise, it will just tell you what method is supported, even if the user is not enrolled. (default false)
}
return new Promise((resolve, reject) => {
//isSupported returns both cases 1. if supported 2. Is enabled/configured/enrolled
TouchID.isSupported(optionalConfigObject)
.then(biometryType => {
// Success code.
// as we are focusing on fingerprint for now
if (biometryType && biometryType != 'FaceID') {
resolve(true);
} else {
let fingerprintLableForOS = Platform.OS == "ios" ? "Touch ID" : "Fingerprint";
reject( fingerprintLableForOS + " is not available on this device");
}
})
.catch(error => {
// iOS Error Format and android error formats are different
// android use code and ios use name
// check at https://github.com/naoufal/react-native-touch-id
let errorCode = Platform.OS == "ios" ? error.name : error.code;
if (errorCode === "LAErrorTouchIDNotEnrolled" || errorCode === "NOT_AVAILABLE" || errorCode === "NOT_ENROLLED") {
let fingerprintLableForOS = Platform.OS == "ios" ? "Touch ID" : "Fingerprint";
resolve(fingerprintLableForOS + " has no enrolled fingers. Please go to settings and enable " + fingerprintLableForOS + " on this device.");
} else {
reject(Platform.OS == "ios" ? error.message : translations.t(error.code));
}
});
});
}
export const authenticateFingerPrint = () => {
return new Promise((resolve, reject) => {
// configuration object for more detailed dialog setup and style:
// const optionalConfigObject = {
// title: 'Authentication Required', // Android
// imageColor: '#e00606', // Android
// imageErrorColor: '#ff0000', // Android
// sensorDescription: 'Touch sensor', // Android
// sensorErrorDescription: 'Failed', // Android
// cancelText: 'Cancel', // Android
// fallbackLabel: 'Show Passcode', // iOS (if empty, then label is hidden)
// unifiedErrors: false, // use unified error messages (default false)
// passcodeFallback: false, // iOS - allows the device to fall back to using the passcode, if faceid/touch is not available. this does not mean that if touchid/faceid fails the first few times it will revert to passcode, rather that if the former are not enrolled, then it will use the passcode.
// };
let fingerprintLableForOS = Platform.OS == "ios" ? "Touch ID" : "Fingerprint";
TouchID.authenticate('Login to [appname] using ' + fingerprintLableForOS)
.then(success => {
// console.log('Authenticated Successfully', success)
resolve(success)
})
.catch(error => {
console.log('Authentication Failed', error.code)
reject(error)
});
});
}
import * as Keychain from 'react-native-keychain';
import { Platform } from "react-native";
export const setCredentials = async (username, password) => {
return new Promise((resolve, reject) => {
// Store the credentials
Keychain.setGenericPassword(username, password)
.then(resp => {
resolve(true)
})
.catch(err => {
console.log("err: ", err);
reject(err);
});
});
}
export const getCredentials = async () => {
return new Promise((resolve, reject) => {
Keychain.getGenericPassword()
.then((credentials) => {
if (credentials && credentials.username) {
// console.log('Credentials successfully loaded for user ' + credentials.username);
resolve(credentials);
} else {
// console.log('No credentials stored');
resolve(null);
}
})
.catch(err => {
console.log("err: ", err);
reject(err);
});
});
}
......@@ -692,9 +692,9 @@
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.7.2":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.5.tgz#665450911c6031af38f81db530f387ec04cd9a98"
integrity sha512-121rumjddw9c3NCQ55KGkyE1h/nzWhU/owjhw0l4mQrkzz4x9SGS1X8gFLraHwX7td3Yo4QTL+qj0NcIzN87BA==
dependencies:
regenerator-runtime "^0.13.4"
......@@ -2590,7 +2590,7 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.1.0:
chalk@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
......@@ -2598,6 +2598,14 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz"
......@@ -6811,10 +6819,10 @@ react-native-autocomplete-input@^5.0.0:
resolved "https://registry.yarnpkg.com/react-native-autocomplete-input/-/react-native-autocomplete-input-5.0.0.tgz#2bcba7be7846fe84e1e753362e3fe40c86fd7c46"
integrity sha512-JsVm1rLNdyf2mLN0cw8XKTP6xwsCw+laPOxb7j81XOZhsS7nQaWytwp6+YEvSi3bnMRuUpvOUbCiS55av9/0/w==
react-native-bootsplash@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-native-bootsplash/-/react-native-bootsplash-3.2.0.tgz#671861fdd75444f91196d3e64d3539ef50c3a30f"
integrity sha512-1+xMWLxqIUPbKiiwrbiekw3uKtuyNpm0R1eg5p3/ISRMXs0eZy7DHfiyCqcLq1N1a3Gv0oTZn1E7WEA2NjgdhA==
react-native-bootsplash@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/react-native-bootsplash/-/react-native-bootsplash-3.2.3.tgz#5f985a492696e14c73dc3b8475be5385b7436593"
integrity sha512-JNuDZ1sL9kppFrZ4OcK/ZM6gf9h5NmM6VfsLJEfa/lSPlD5wXoOyXIaeBn/6odaIPxSp31WIryeaNiZZIltu7g==
dependencies:
chalk "^4.1.0"
fs-extra "^9.1.0"
......@@ -6884,6 +6892,11 @@ react-native-iphone-x-helper@^1.3.0:
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz"
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
react-native-keychain@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-7.0.0.tgz#11fd559fe86ae89f934b8c75ae228247deed5494"
integrity sha512-tH26sgW4OxB/llXmhO+DajFISEUoF1Ip2+WSDMIgCt8SP1xRE81m2qFzgIOc/7StYsUERxHhDPkxvq2H0/Goig==
react-native-linear-gradient@^2.5.6:
version "2.5.6"
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz#96215cbc5ec7a01247a20890888aa75b834d44a0"
......@@ -6936,6 +6949,11 @@ react-native-screens@^2.17.1:
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.18.0.tgz"
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:
version "2.7.4"
resolved "https://registry.yarnpkg.com/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.4.tgz#86e2dbe8af9e6bf60eee088f60466f7a975e7758"
......@@ -6953,6 +6971,11 @@ react-native-tab-view@^2.15.2:
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-2.15.2.tgz"
integrity sha512-2hxLkBnZtEKFDyfvNO5EUywhy3f/EiLOBO8SWqKj4BMBTO0QwnybaPE5MVF00Fhz+VA4+h/iI40Dkrrtq70dGg==
react-native-touch-id@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-native-touch-id/-/react-native-touch-id-4.4.1.tgz#8b1bb2d04c30bac36bb9696d2d723e719c4a8b08"
integrity sha512-1jTl8fC+0fxvqegy/XXTyo6vMvPhjzkoDdaqoYZx0OH8AT250NuXnNPyKktvigIcys3+2acciqOeaCall7lrvg==
react-native-vector-icons@^8.0.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-8.1.0.tgz"
......
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