Untitled
unknown
plain_text
a month ago
15 kB
1
Indexable
Never
import React, {useCallback, useEffect, useMemo, useState} from 'react'; import { ActivityIndicator, DeviceEventEmitter, Dimensions, Image, Platform, Text, TouchableOpacity, View, } from 'react-native'; import ImageResizer from '@bam.tech/react-native-image-resizer'; import ImageEditor from '@react-native-community/image-editor'; import { startLightSensor, stopLightSensor, } from 'react-native-ambient-light-sensor'; import {RNCamera} from 'react-native-camera'; import FastImage from 'react-native-fast-image'; import OrangeWarningIcon from '@assets/kyc/orange_warning.webp'; import {BackImage, NavigationButton} from '@components/Atom'; import BoxLockSaveKYC from '@components/Atom/BoxLockSaveKYC'; import {useLocale} from '@hooks/global/useLocale'; import useModal from '@hooks/global/useModal'; import useFirebaseRemoteConfig, { FirebaseRemoteKycSettingsType, } from '@hooks/local/useFirebaseRemoteConfig'; import useHelpCenter from '@hooks/local/useHelpCenter'; import useKTPUploadLog, {LogImageKTPReason} from '@hooks/local/useKTPUploadLog'; import useSegmentAnalytics from '@hooks/local/useSegmentAnalytics'; import {AppStackProps} from '@navigator/AppParamList'; import {isAndroid} from '@utils/iPhoneXHelpers'; import styles, {cropHeight, cropLeft, cropTop, cropWidth} from './style'; const windowWidth = Dimensions.get('window').width; const windowHeight = Dimensions.get('window').height; const KYCKTPCameraScreen = ({ navigation, route, }: AppStackProps<'KYCKTPCamera'>) => { let camera: RNCamera | null = null; const [ratio, setRatio] = React.useState<string>('16:9'); const {translate: t} = useLocale(); const {trackEvent} = useSegmentAnalytics('KYCScanKTPPage'); const {showHelpCenter} = useHelpCenter(); const [loading, setLoading] = useState<boolean>(false); const [, showAlertModal] = useModal( _ => undefined, action => action.showAlertModal, ); const [, hideModal] = useModal( _ => undefined, action => action.hideModal, ); const {doLogErrorUploadKTP} = useKTPUploadLog(); const {getFirebaseRemoteConfigData} = useFirebaseRemoteConfig(); const remoteConfig = useMemo( () => getFirebaseRemoteConfigData<FirebaseRemoteKycSettingsType>( 'kyc_settings', ), [getFirebaseRemoteConfigData], ); const onPressHelp = useCallback(() => { showHelpCenter({}); }, [showHelpCenter]); const [result, setResult] = React.useState<number>(0); useEffect(() => { if (Platform.OS === 'android') { startLightSensor(); const subscription = DeviceEventEmitter.addListener( 'LightSensor', (data: {lightValue: number}) => { setResult(data.lightValue); }, ); return () => { stopLightSensor(); subscription?.remove(); }; } }, []); useEffect(() => { navigation.setOptions({ headerTransparent: true, headerStyle: styles.headerText, headerRight: () => ( <NavigationButton text={t('help')} onPress={() => { onPressHelp(); }} /> ), headerLeft: () => ( <TouchableOpacity activeOpacity={0.9} onPress={() => { navigation.goBack(); }}> <BackImage /> </TouchableOpacity> ), }); navigation.setParams({flow: 'KYC'}); }, [navigation, onPressHelp, t]); const prepareRatio = () => { if (camera !== null && Platform.OS === 'android') { camera.getSupportedRatiosAsync().then(ratios => { const findRatio = ratios.find(item => { return item === '16:9'; }); setRatio(findRatio ? findRatio : ratios[ratios.length - 2]); }); } }; const showModalError = useCallback( ({error}: {error: 'sizing' | 'resolution'}) => { const errorMessage = error === 'sizing' ? t('kyc_size_error') : t('kyc_resolution_error', { minPx: remoteConfig?.kycKtpUpload.kycImageWidthMinimum, maxPx: remoteConfig?.kycKtpUpload.kycImageWidthMaximum, }); setTimeout(() => { showAlertModal({ visible: true, title: t('kyc_general_error.title'), subTitle: errorMessage, primaryText: t('button.contact_us'), image: OrangeWarningIcon, secondaryText: t('retry'), onSecondaryPress: hideModal, onPrimaryPress: onPressHelp, onBackdropPress: hideModal, }); }, 500); }, [ t, remoteConfig?.kycKtpUpload.kycImageWidthMinimum, remoteConfig?.kycKtpUpload.kycImageWidthMaximum, showAlertModal, hideModal, onPressHelp, ], ); const visibleGuideline = useCallback(() => { if ( Platform.OS === 'android' && (result < (remoteConfig?.kycKtpUpload.ambientDarkTreshold ?? 51) || result > (remoteConfig?.kycKtpUpload.ambientBrightTreshold ?? 5000)) && remoteConfig?.kycKtpUpload.takePhotoButtonValidation ) { return ( <> <View style={styles.guidelineErrorContainer}> <FastImage style={styles.imageGuide} source={require('@assets/kyc/light_guide.webp')} /> <Text style={styles.guidelineText}> {result < (remoteConfig?.kycKtpUpload.ambientDarkTreshold ?? 51) && t('kyc.guidelineErrorTooDark')} {result > (remoteConfig?.kycKtpUpload.ambientBrightTreshold ?? 5000) && t('kyc.guidelineErrorTooBright')} </Text> </View> <View style={styles.boxContainer}> {remoteConfig?.kycKtpUpload.showingThresholdResult && ( <Text style={styles.title}>{result}</Text> )} <BoxLockSaveKYC /> </View> </> ); } return ( <> <Text style={styles.text}>{t('kyc.guidelineTakeKTPPic')}</Text> <View style={styles.boxContaineriOS}> <BoxLockSaveKYC /> </View> </> ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ remoteConfig?.kycKtpUpload.ambientBrightTreshold, remoteConfig?.kycKtpUpload.ambientDarkTreshold, remoteConfig?.kycKtpUpload.takePhotoButtonValidation, result, t, ]); const takePicture = async () => { if (camera) { if ( ((result > (remoteConfig?.kycKtpUpload.ambientBrightTreshold ?? 5000) || result < (remoteConfig?.kycKtpUpload.ambientDarkTreshold ?? 51)) && Platform.OS === 'android' && remoteConfig?.kycKtpUpload.takePhotoButtonValidation) || loading ) { return; } setLoading(true); const options = { quality: 1, base64: true, orientation: RNCamera.Constants.Orientation.portrait, fixOrientation: true, }; try { // Taking the picture const heightAdjustment = Platform.OS === 'ios' ? 0 : 20; const widthAdjustment = Platform.OS === 'ios' ? 0 : 5; const data = await camera.takePictureAsync(options); const widthScale = data.width / windowWidth; const heightScale = data.height / windowHeight; // Crop & resizing image const imageWidth = widthScale * cropWidth - widthAdjustment; const imageHeight = heightScale * cropHeight - heightAdjustment; const uri = await ImageEditor.cropImage(data.uri, { offset: { x: widthScale * cropLeft - 50, y: heightScale * cropTop - 100, }, size: { width: imageWidth + 100, height: imageHeight + 100, }, displaySize: { width: widthScale * cropWidth, height: heightScale * cropHeight, }, }); const kycImageWidthMinimum = remoteConfig?.kycKtpUpload?.kycImageWidthMinimum ?? 0; const kycImageHeightMinimum = remoteConfig?.kycKtpUpload?.kycImageHeightMinimum ?? 0; const kycImageWidthMaximum = remoteConfig?.kycKtpUpload?.kycImageWidthMaximum ?? 0; const kycImageHeightMaximum = remoteConfig?.kycKtpUpload?.kycImageHeightMaximum ?? 0; if ( imageWidth < kycImageWidthMinimum || imageHeight < kycImageHeightMinimum || imageWidth > kycImageWidthMaximum || imageHeight > kycImageHeightMaximum ) { if ( imageWidth < kycImageWidthMinimum || imageHeight < kycImageHeightMinimum ) { doLogErrorUploadKTP(LogImageKTPReason.ImageResolutionUnderLimit); } else if ( imageWidth > kycImageWidthMaximum || imageHeight > kycImageHeightMaximum ) { doLogErrorUploadKTP(LogImageKTPReason.ImageResolutionOverLimit); } showModalError({error: 'resolution'}); setLoading(false); return; } /** * on serveral device there is unknown way that got 432 px for the height * so we add buffer crop resize in order match with AAI requirement */ const kycImageBufferWidth = remoteConfig?.kycKtpUpload?.kycImageBufferWidth || 0; const kycImageBufferHeight = remoteConfig?.kycKtpUpload?.kycImageBufferHeight || 0; // Compressing image const compressionWidth = widthScale * cropWidth + (isAndroid() ? kycImageBufferWidth : 0); const compressionHeight = heightScale * cropTop + (isAndroid() ? kycImageBufferHeight : 0); let compression = 50; let compressedImage = await ImageResizer.createResizedImage( uri.uri, compressionWidth, compressionHeight, 'JPEG', compression, ); /* If Image File Size is Under the Minimum Size */ if ( remoteConfig?.kycKtpUpload.kycImageMinFileSize && compressedImage.size < remoteConfig?.kycKtpUpload.kycImageMinFileSize ) { for ( let _compression = compression / 10; _compression <= 10; _compression++ ) { compressedImage = await ImageResizer.createResizedImage( uri.uri, compressionWidth, compressionHeight, 'JPEG', _compression * 10, ); if ( compressedImage.size >= remoteConfig?.kycKtpUpload.kycImageMinFileSize ) { break; } } if ( compressedImage.size < remoteConfig?.kycKtpUpload.kycImageMinFileSize ) { doLogErrorUploadKTP(LogImageKTPReason.ImageSizeUnderLimit); showModalError({error: 'sizing'}); setLoading(false); return; } } /* If Image File Size is Larger than Maximum */ if ( remoteConfig?.kycKtpUpload.kycImageMinFileSize && compressedImage.size > remoteConfig?.kycKtpUpload.kycImageMaxFileSize ) { for ( let _compression = compression / 10; _compression > 0; _compression-- ) { compressedImage = await ImageResizer.createResizedImage( uri.uri, compressionWidth, compressionHeight, 'JPEG', _compression * 10, ); if ( compressedImage.size <= remoteConfig?.kycKtpUpload.kycImageMaxFileSize ) { break; } } if ( compressedImage.size > remoteConfig?.kycKtpUpload.kycImageMaxFileSize ) { doLogErrorUploadKTP(LogImageKTPReason.ImageSizeOverLimit); showModalError({error: 'sizing'}); setLoading(false); return; } } trackEvent('KYCScanKTPTakePictureUpload'); navigation.replace('KTPConfirm', { url: compressedImage.uri, completion: route.params.completion, }); } catch (error) { console.log('error', error); doLogErrorUploadKTP( `${ LogImageKTPReason.Others }, KYCKTPCamera.tsx | takePicture() | error ->', ${String(error)}`, ); setLoading(false); } } }; return ( <> <View style={styles.container}> <RNCamera ref={ref => { camera = ref; }} captureAudio={false} style={styles.preview} ratio={Platform.select({ios: undefined, android: ratio})} type={RNCamera.Constants.Type.back} flashMode={RNCamera.Constants.FlashMode.off} onCameraReady={prepareRatio} androidCameraPermissionOptions={{ title: t('kyc.accessCameraTip'), message: t('kyc.accessGallerySubTip'), buttonPositive: 'Ok', buttonNegative: t('kyc.notAllow'), }} androidRecordAudioPermissionOptions={{ title: t('kyc.accessCameraTip'), message: t('kyc.accessGallerySubTip'), buttonPositive: 'Ok', buttonNegative: t('kyc.notAllow'), }} /> </View> <Text style={styles.title}>{t('kyc.takeKTPPicture')}</Text> <View style={styles.guideline}>{visibleGuideline()}</View> <View style={styles.captureContainer}> <TouchableOpacity onPress={() => { takePicture(); }} style={styles.capture}> {loading ? ( <ActivityIndicator size={'large'} style={styles.button} /> ) : ( <Image style={styles.button} source={require('@assets/kyc/capture_button.webp')} /> )} </TouchableOpacity> </View> <View style={styles.cropContainer}> <View style={styles.rectangle} /> </View> <View style={styles.overlayTop} /> <View style={styles.overlayRight} /> <View style={styles.overlayBottom} /> <View style={styles.overlayLeft} /> {/* corner */} <View style={styles.leftTopCorner} /> <View style={styles.rightTopCorner} /> <View style={styles.rightBottomCorner} /> <View style={styles.leftBottomCorner} /> {/* triangle */} <View style={styles.leftTopTriangle} /> <View style={styles.rightTopTriangle} /> <View style={styles.rightBottomTriangle} /> <View style={styles.leftBottomTriangle} /> </> ); }; export default KYCKTPCameraScreen;
Leave a Comment