Untitled

mail@pastecode.io avatar
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