react-native-timacare 3.3.56-alert → 3.3.58-cccd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/assets/icons/index.js +1 -1
- package/lib/commonjs/assets/icons/index.js.flow +2 -0
- package/lib/commonjs/assets/icons/index.js.map +1 -1
- package/lib/commonjs/assets/icons/modal_confirm.svg +5 -5
- package/lib/commonjs/assets/icons/modal_notice.svg +32 -32
- package/lib/commonjs/assets/icons/modal_success.svg +26 -26
- package/lib/commonjs/assets/icons/modal_warning.svg +21 -21
- package/lib/commonjs/assets/icons/user_security.svg +10 -0
- package/lib/commonjs/assets/tts/Frame1000011947.png +0 -0
- package/lib/commonjs/components/AlertLoan.js +1 -1
- package/lib/commonjs/components/AlertLoan.js.flow +1 -1
- package/lib/commonjs/components/AlertLoan.js.map +1 -1
- package/lib/commonjs/components/AppModal.js +1 -1
- package/lib/commonjs/components/AppModal.js.flow +40 -2
- package/lib/commonjs/components/AppModal.js.map +1 -1
- package/lib/commonjs/components/ItemTimaOne.js +1 -1
- package/lib/commonjs/components/ItemTimaOne.js.flow +1 -1
- package/lib/commonjs/components/ItemTimaOne.js.map +1 -1
- package/lib/commonjs/components/ModalNotification/AlertGlobal.js +1 -1
- package/lib/commonjs/components/ModalNotification/AlertGlobal.js.flow +92 -33
- package/lib/commonjs/components/ModalNotification/AlertGlobal.js.map +1 -1
- package/lib/commonjs/components/ModalNotification/index.js +1 -1
- package/lib/commonjs/components/ModalNotification/index.js.flow +7 -0
- package/lib/commonjs/components/ModalNotification/index.js.map +1 -1
- package/lib/commonjs/components/ModalOptionLoan.js +1 -1
- package/lib/commonjs/components/ModalOptionLoan.js.flow +1 -1
- package/lib/commonjs/components/ModalOptionLoan.js.map +1 -1
- package/lib/commonjs/components/ModalOptionLoanDigital.js +1 -1
- package/lib/commonjs/components/ModalOptionLoanDigital.js.flow +1 -1
- package/lib/commonjs/components/ModalOptionLoanDigital.js.map +1 -1
- package/lib/commonjs/components/modalStack.js +2 -0
- package/lib/commonjs/components/modalStack.js.flow +47 -0
- package/lib/commonjs/components/modalStack.js.map +1 -0
- package/lib/commonjs/navigation/primary-navigator.js +1 -1
- package/lib/commonjs/navigation/primary-navigator.js.flow +7 -0
- package/lib/commonjs/navigation/primary-navigator.js.map +1 -1
- package/lib/commonjs/screens/NFC/OcrStart.js +2 -0
- package/lib/commonjs/screens/NFC/OcrStart.js.flow +171 -0
- package/lib/commonjs/screens/NFC/OcrStart.js.map +1 -0
- package/lib/commonjs/screens/NFC/ScanMRZ.js +2 -0
- package/lib/commonjs/screens/NFC/ScanMRZ.js.flow +563 -0
- package/lib/commonjs/screens/NFC/ScanMRZ.js.map +1 -0
- package/lib/commonjs/screens/camera/CCCDCameraScreen.js +1 -1
- package/lib/commonjs/screens/camera/CCCDCameraScreen.js.flow +203 -133
- package/lib/commonjs/screens/camera/CCCDCameraScreen.js.map +1 -1
- package/lib/commonjs/screens/camera/index.js +1 -1
- package/lib/commonjs/screens/camera/index.js.flow +1 -1
- package/lib/commonjs/screens/camera/index.js.map +1 -1
- package/lib/commonjs/screens/detail-loan/PaymentInfo.js +1 -1
- package/lib/commonjs/screens/detail-loan/PaymentInfo.js.flow +1 -1
- package/lib/commonjs/screens/detail-loan/PaymentInfo.js.map +1 -1
- package/lib/commonjs/screens/detail-loan/ShowQrCode.js +1 -1
- package/lib/commonjs/screens/detail-loan/ShowQrCode.js.flow +1 -1
- package/lib/commonjs/screens/detail-loan/ShowQrCode.js.map +1 -1
- package/lib/commonjs/screens/digital-lending/Step6.js +1 -1
- package/lib/commonjs/screens/digital-lending/Step6.js.flow +1 -1
- package/lib/commonjs/screens/digital-lending/Step6.js.map +1 -1
- package/lib/commonjs/screens/eSign/index.js +1 -1
- package/lib/commonjs/screens/eSign/index.js.flow +1 -1
- package/lib/commonjs/screens/eSign/index.js.map +1 -1
- package/lib/commonjs/screens/home/DigitalTopupItem.js +1 -1
- package/lib/commonjs/screens/home/DigitalTopupItem.js.flow +1 -1
- package/lib/commonjs/screens/home/DigitalTopupItem.js.map +1 -1
- package/lib/commonjs/screens/home/ItemMotoBikeLoan.js +1 -1
- package/lib/commonjs/screens/home/ItemMotoBikeLoan.js.flow +1 -1
- package/lib/commonjs/screens/home/ItemMotoBikeLoan.js.map +1 -1
- package/lib/commonjs/screens/home/index.js +1 -1
- package/lib/commonjs/screens/home/index.js.flow +8 -113
- package/lib/commonjs/screens/home/index.js.map +1 -1
- package/lib/commonjs/screens/nationalID/index.js +1 -1
- package/lib/commonjs/screens/nationalID/index.js.flow +123 -218
- package/lib/commonjs/screens/nationalID/index.js.map +1 -1
- package/lib/commonjs/screens/nationalIDBack/index.js +1 -1
- package/lib/commonjs/screens/nationalIDBack/index.js.flow +129 -209
- package/lib/commonjs/screens/nationalIDBack/index.js.map +1 -1
- package/lib/commonjs/screens/payment-evidence/ModalGuideline.js +1 -1
- package/lib/commonjs/screens/payment-evidence/ModalGuideline.js.flow +1 -1
- package/lib/commonjs/screens/payment-evidence/ModalGuideline.js.map +1 -1
- package/lib/commonjs/screens/toan-trinh-so/OCR.js +1 -1
- package/lib/commonjs/screens/toan-trinh-so/OCR.js.flow +270 -556
- package/lib/commonjs/screens/toan-trinh-so/OCR.js.map +1 -1
- package/lib/commonjs/screens/toan-trinh-so/RegisterCamera.js +1 -1
- package/lib/commonjs/screens/toan-trinh-so/RegisterCamera.js.flow +1 -1
- package/lib/commonjs/screens/toan-trinh-so/RegisterCamera.js.map +1 -1
- package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js +1 -1
- package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js.flow +226 -288
- package/lib/commonjs/screens/toan-trinh-so/VehicleCamera.js.map +1 -1
- package/lib/commonjs/utils/cameraCrop.js +2 -0
- package/lib/commonjs/utils/cameraCrop.js.flow +96 -0
- package/lib/commonjs/utils/cameraCrop.js.map +1 -0
- package/lib/module/assets/icons/index.js +1 -1
- package/lib/module/assets/icons/index.js.map +1 -1
- package/lib/module/assets/icons/modal_confirm.svg +5 -5
- package/lib/module/assets/icons/modal_notice.svg +32 -32
- package/lib/module/assets/icons/modal_success.svg +26 -26
- package/lib/module/assets/icons/modal_warning.svg +21 -21
- package/lib/module/assets/icons/user_security.svg +10 -0
- package/lib/module/assets/tts/Frame1000011947.png +0 -0
- package/lib/module/components/AlertLoan.js +1 -1
- package/lib/module/components/AlertLoan.js.map +1 -1
- package/lib/module/components/AppModal.js +1 -1
- package/lib/module/components/AppModal.js.map +1 -1
- package/lib/module/components/ItemTimaOne.js +1 -1
- package/lib/module/components/ItemTimaOne.js.map +1 -1
- package/lib/module/components/ModalNotification/AlertGlobal.js +1 -1
- package/lib/module/components/ModalNotification/AlertGlobal.js.map +1 -1
- package/lib/module/components/ModalNotification/index.js +1 -1
- package/lib/module/components/ModalNotification/index.js.map +1 -1
- package/lib/module/components/ModalOptionLoan.js +1 -1
- package/lib/module/components/ModalOptionLoan.js.map +1 -1
- package/lib/module/components/ModalOptionLoanDigital.js +1 -1
- package/lib/module/components/ModalOptionLoanDigital.js.map +1 -1
- package/lib/module/components/modalStack.js +2 -0
- package/lib/module/components/modalStack.js.map +1 -0
- package/lib/module/navigation/primary-navigator.js +1 -1
- package/lib/module/navigation/primary-navigator.js.map +1 -1
- package/lib/module/screens/NFC/OcrStart.js +2 -0
- package/lib/module/screens/NFC/OcrStart.js.map +1 -0
- package/lib/module/screens/NFC/ScanMRZ.js +2 -0
- package/lib/module/screens/NFC/ScanMRZ.js.map +1 -0
- package/lib/module/screens/camera/CCCDCameraScreen.js +1 -1
- package/lib/module/screens/camera/CCCDCameraScreen.js.map +1 -1
- package/lib/module/screens/camera/index.js +1 -1
- package/lib/module/screens/camera/index.js.map +1 -1
- package/lib/module/screens/detail-loan/PaymentInfo.js +1 -1
- package/lib/module/screens/detail-loan/PaymentInfo.js.map +1 -1
- package/lib/module/screens/detail-loan/ShowQrCode.js +1 -1
- package/lib/module/screens/detail-loan/ShowQrCode.js.map +1 -1
- package/lib/module/screens/digital-lending/Step6.js +1 -1
- package/lib/module/screens/digital-lending/Step6.js.map +1 -1
- package/lib/module/screens/eSign/index.js +1 -1
- package/lib/module/screens/eSign/index.js.map +1 -1
- package/lib/module/screens/home/DigitalTopupItem.js +1 -1
- package/lib/module/screens/home/DigitalTopupItem.js.map +1 -1
- package/lib/module/screens/home/ItemMotoBikeLoan.js +1 -1
- package/lib/module/screens/home/ItemMotoBikeLoan.js.map +1 -1
- package/lib/module/screens/home/index.js +1 -1
- package/lib/module/screens/home/index.js.map +1 -1
- package/lib/module/screens/nationalID/index.js +1 -1
- package/lib/module/screens/nationalID/index.js.map +1 -1
- package/lib/module/screens/nationalIDBack/index.js +1 -1
- package/lib/module/screens/nationalIDBack/index.js.map +1 -1
- package/lib/module/screens/payment-evidence/ModalGuideline.js +1 -1
- package/lib/module/screens/payment-evidence/ModalGuideline.js.map +1 -1
- package/lib/module/screens/toan-trinh-so/OCR.js +1 -1
- package/lib/module/screens/toan-trinh-so/OCR.js.map +1 -1
- package/lib/module/screens/toan-trinh-so/RegisterCamera.js +1 -1
- package/lib/module/screens/toan-trinh-so/RegisterCamera.js.map +1 -1
- package/lib/module/screens/toan-trinh-so/VehicleCamera.js +1 -1
- package/lib/module/screens/toan-trinh-so/VehicleCamera.js.map +1 -1
- package/lib/module/utils/cameraCrop.js +2 -0
- package/lib/module/utils/cameraCrop.js.map +1 -0
- package/lib/typescript/assets/icons/index.d.ts +2 -1
- package/lib/typescript/assets/icons/index.d.ts.map +1 -1
- package/lib/typescript/components/AppModal.d.ts +6 -1
- package/lib/typescript/components/AppModal.d.ts.map +1 -1
- package/lib/typescript/components/ModalNotification/AlertGlobal.d.ts +20 -4
- package/lib/typescript/components/ModalNotification/AlertGlobal.d.ts.map +1 -1
- package/lib/typescript/components/ModalNotification/index.d.ts +6 -1
- package/lib/typescript/components/ModalNotification/index.d.ts.map +1 -1
- package/lib/typescript/components/modalStack.d.ts +11 -0
- package/lib/typescript/components/modalStack.d.ts.map +1 -0
- package/lib/typescript/navigation/primary-navigator.d.ts +3 -0
- package/lib/typescript/navigation/primary-navigator.d.ts.map +1 -1
- package/lib/typescript/screens/NFC/OcrStart.d.ts +2 -0
- package/lib/typescript/screens/NFC/OcrStart.d.ts.map +1 -0
- package/lib/typescript/screens/NFC/ScanMRZ.d.ts +15 -0
- package/lib/typescript/screens/NFC/ScanMRZ.d.ts.map +1 -0
- package/lib/typescript/screens/camera/CCCDCameraScreen.d.ts.map +1 -1
- package/lib/typescript/screens/home/index.d.ts.map +1 -1
- package/lib/typescript/screens/nationalID/index.d.ts.map +1 -1
- package/lib/typescript/screens/nationalIDBack/index.d.ts.map +1 -1
- package/lib/typescript/screens/toan-trinh-so/OCR.d.ts.map +1 -1
- package/lib/typescript/screens/toan-trinh-so/VehicleCamera.d.ts.map +1 -1
- package/lib/typescript/utils/cameraCrop.d.ts +43 -0
- package/lib/typescript/utils/cameraCrop.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/assets/icons/index.tsx +2 -0
- package/src/assets/icons/modal_confirm.svg +5 -5
- package/src/assets/icons/modal_notice.svg +32 -32
- package/src/assets/icons/modal_success.svg +26 -26
- package/src/assets/icons/modal_warning.svg +21 -21
- package/src/assets/icons/user_security.svg +10 -0
- package/src/assets/tts/Frame1000011947.png +0 -0
- package/src/components/AlertLoan.tsx +1 -1
- package/src/components/AppModal.tsx +40 -2
- package/src/components/ItemTimaOne.tsx +1 -1
- package/src/components/ModalNotification/AlertGlobal.tsx +92 -33
- package/src/components/ModalNotification/index.tsx +7 -0
- package/src/components/ModalOptionLoan.tsx +1 -1
- package/src/components/ModalOptionLoanDigital.tsx +1 -1
- package/src/components/modalStack.ts +47 -0
- package/src/navigation/primary-navigator.tsx +7 -0
- package/src/screens/NFC/OcrStart.tsx +171 -0
- package/src/screens/NFC/ScanMRZ.tsx +563 -0
- package/src/screens/camera/CCCDCameraScreen.tsx +203 -133
- package/src/screens/camera/index.tsx +1 -1
- package/src/screens/detail-loan/PaymentInfo.tsx +1 -1
- package/src/screens/detail-loan/ShowQrCode.tsx +1 -1
- package/src/screens/digital-lending/Step6.tsx +1 -1
- package/src/screens/eSign/index.tsx +1 -1
- package/src/screens/home/DigitalTopupItem.tsx +1 -1
- package/src/screens/home/ItemMotoBikeLoan.tsx +1 -1
- package/src/screens/home/index.tsx +8 -113
- package/src/screens/nationalID/index.tsx +123 -218
- package/src/screens/nationalIDBack/index.tsx +129 -209
- package/src/screens/payment-evidence/ModalGuideline.tsx +1 -1
- package/src/screens/toan-trinh-so/OCR.tsx +270 -556
- package/src/screens/toan-trinh-so/RegisterCamera.tsx +1 -1
- package/src/screens/toan-trinh-so/VehicleCamera.tsx +226 -288
- package/src/utils/cameraCrop.ts +96 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.computeCropRegion=void 0;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));var _regenerator=_interopRequireDefault(require("@babel/runtime/regenerator"));var _mobxReactLite=require("mobx-react-lite");var _react=_interopRequireWildcard(require("react"));var _reactNative=require("react-native");var _reactNativeMrzScanner=require("react-native-mrz-scanner");var _reactNativeVisionCamera=require("react-native-vision-camera");var _reactNativeImageManipulator=_interopRequireDefault(require("@oguzhnatly/react-native-image-manipulator"));var _reactNativeImageCropPicker=_interopRequireDefault(require("react-native-image-crop-picker"));var _reactNativeImagePicker=require("react-native-image-picker");var _AlertGlobal=require("../../components/ModalNotification/AlertGlobal");var _reactNativeSafeAreaContext=require("react-native-safe-area-context");var _native=require("@react-navigation/native");var _icons=require("../../assets/icons");var _svgs=require("../../assets/svgs");var _MText=require("../../components/MText");var _this=this,_jsxFileName="/Users/tima/Tima/react-native-timacare/src/screens/NFC/ScanMRZ.tsx";function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap(),n=new WeakMap();return(_interopRequireWildcard=function _interopRequireWildcard(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f);}for(var _t3 in e)"default"!==_t3&&{}.hasOwnProperty.call(e,_t3)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,_t3))&&(i.get||i.set)?o(f,_t3,i):f[_t3]=e[_t3]);return f;})(e,t);}var NFCReader=_reactNative.NativeModules.NFCReader;var ORANGE='#F05123';var CARD_ASPECT_RATIO=85.6/54;var getNfcStatus=function _callee(){var status,_t;return _regenerator.default.async(function(_context){while(1)switch(_context.prev=_context.next){case 0:if(!(_reactNative.Platform.OS==='ios')){_context.next=1;break;}return _context.abrupt("return",{supported:true,enabled:true});case 1:_context.prev=1;_context.next=2;return _regenerator.default.awrap(NFCReader==null?void 0:NFCReader.getNfcStatus==null?void 0:NFCReader.getNfcStatus());case 2:status=_context.sent;return _context.abrupt("return",{supported:!!(status!=null&&status.supported),enabled:!!(status!=null&&status.enabled)});case 3:_context.prev=3;_t=_context["catch"](1);return _context.abrupt("return",{supported:false,enabled:false});case 4:case"end":return _context.stop();}},null,null,[[1,3]],Promise);};var promptEnableNfc=function promptEnableNfc(){_AlertGlobal.AlertGlobal.show({type:'warning',title:'NFC đang tắt',message:'Thiết bị của bạn hỗ trợ NFC nhưng đang tắt. Vui lòng bật NFC để đọc thông tin thẻ CCCD.',primaryText:'Mở cài đặt',secondaryText:'Để sau',onPrimaryPress:function onPrimaryPress(){_reactNative.Linking.sendIntent('android.settings.NFC_SETTINGS').catch(function(){});}});};var getImageSize=function getImageSize(uri){return new Promise(function(resolve,reject){_reactNative.Image.getSize(uri,function(width,height){return resolve({width:width,height:height});},reject);});};var computeCropRegion=exports.computeCropRegion=function computeCropRegion(photoW,photoH,frameW,frameH){var rotated=photoW>photoH!==frameW>frameH;var w=rotated?photoH:photoW;var h=rotated?photoW:photoH;var scale=Math.max(frameW/w,frameH/h);var cropW=Math.min(w,Math.round(frameW/scale));var cropH=Math.min(h,Math.round(frameH/scale));var cropX=Math.max(0,Math.round((w-cropW)/2));var cropY=Math.max(0,Math.round((h-cropH)/2));return rotated?{originX:cropY,originY:cropX,width:cropH,height:cropW}:{originX:cropX,originY:cropY,width:cropW,height:cropH};};var ScanMRZ=function ScanMRZ(props){var _props$route$params$i,_props$route,_props$route$params,_props$route2,_props$route2$params;var insets=(0,_reactNativeSafeAreaContext.useSafeAreaInsets)();var navigation=(0,_native.useNavigation)();var isFocused=(0,_native.useIsFocused)();var scannerRef=(0,_react.useRef)(null);var cameraRef=(0,_react.useRef)(null);var frameLayout=(0,_react.useRef)({width:0,height:0});var _useState=(0,_react.useState)((_props$route$params$i=(_props$route=props.route)==null?void 0:(_props$route$params=_props$route.params)==null?void 0:_props$route$params.initialMode)!=null?_props$route$params$i:null),_useState2=(0,_slicedToArray2.default)(_useState,2),mode=_useState2[0],setMode=_useState2[1];var _useState3=(0,_react.useState)(true),_useState4=(0,_slicedToArray2.default)(_useState3,2),autoCapture=_useState4[0],setAutoCapture=_useState4[1];var _useState5=(0,_react.useState)(_reactNative.Platform.OS==='ios'),_useState6=(0,_slicedToArray2.default)(_useState5,2),hasPermission=_useState6[0],setHasPermission=_useState6[1];var _useState7=(0,_react.useState)(false),_useState8=(0,_slicedToArray2.default)(_useState7,2),cameraReady=_useState8[0],setCameraReady=_useState8[1];var _useState9=(0,_react.useState)(false),_useState0=(0,_slicedToArray2.default)(_useState9,2),taking=_useState0[0],setTaking=_useState0[1];var _useState1=(0,_react.useState)(null),_useState10=(0,_slicedToArray2.default)(_useState1,2),captured=_useState10[0],setCaptured=_useState10[1];var device=(0,_reactNativeVisionCamera.useCameraDevice)('back');var format=(0,_reactNativeVisionCamera.useCameraFormat)(device,[{photoResolution:{width:1280,height:720}}]);var front=((_props$route2=props.route)==null?void 0:(_props$route2$params=_props$route2.params)==null?void 0:_props$route2$params.front)!==false;(0,_react.useEffect)(function(){if(_reactNative.Platform.OS==='android'){_reactNative.PermissionsAndroid.request(_reactNative.PermissionsAndroid.PERMISSIONS.CAMERA,{title:'Quyền camera',message:'Ứng dụng cần camera để quét chuỗi ký tự trên mặt sau thẻ CCCD.',buttonPositive:'Đồng ý',buttonNegative:'Từ chối'}).then(function(status){return setHasPermission(status===_reactNative.PermissionsAndroid.RESULTS.GRANTED);});}},[]);(0,_react.useEffect)(function(){var _props$route3,_props$route3$params;if((_props$route3=props.route)!=null&&(_props$route3$params=_props$route3.params)!=null&&_props$route3$params.initialMode)return;getNfcStatus().then(function(_ref){var supported=_ref.supported,enabled=_ref.enabled;setMode(supported?'nfc':'photo');if(supported&&!enabled){promptEnableNfc();}});},[]);var switchToNfc=function _callee2(){var _await$getNfcStatus,supported,enabled;return _regenerator.default.async(function(_context2){while(1)switch(_context2.prev=_context2.next){case 0:setMode('nfc');_context2.next=1;return _regenerator.default.awrap(getNfcStatus());case 1:_await$getNfcStatus=_context2.sent;supported=_await$getNfcStatus.supported;enabled=_await$getNfcStatus.enabled;if(supported&&!enabled){promptEnableNfc();}case 2:case"end":return _context2.stop();}},null,null,null,Promise);};(0,_react.useEffect)(function(){setCameraReady(false);var timeout=setTimeout(function(){return setCameraReady(true);},500);return function(){return clearTimeout(timeout);};},[mode]);var handleDetected=(0,_react.useCallback)(function(result){var _result$idNumber;_reactNative.Alert.alert('Đã đọc MRZ',["H\u1ECD t\xEAn: "+result.fullName,"S\u1ED1 \u0111\u1ECBnh danh: "+((_result$idNumber=result.idNumber)!=null?_result$idNumber:result.documentNumber),"Ng\xE0y sinh: "+result.dateOfBirth,"H\u1EBFt h\u1EA1n: "+result.dateOfExpiry,"H\u1EE3p l\u1EC7: "+(result.valid?'Có':'Không')].join('\n'),[{text:'Quét lại',onPress:function onPress(){var _scannerRef$current;return(_scannerRef$current=scannerRef.current)==null?void 0:_scannerRef$current.resume();}}]);},[]);var handleError=(0,_react.useCallback)(function(error){_AlertGlobal.AlertGlobal.show({type:'warning',title:'Thông báo',message:error==null?void 0:error.message});},[]);var cropToFrame=function _callee3(uri){var _frameLayout$current,frameW,frameH,_await$getImageSize,photoW,photoH,crop,result;return _regenerator.default.async(function(_context3){while(1)switch(_context3.prev=_context3.next){case 0:_frameLayout$current=frameLayout.current,frameW=_frameLayout$current.width,frameH=_frameLayout$current.height;if(!(!frameW||!frameH)){_context3.next=1;break;}return _context3.abrupt("return",uri);case 1:_context3.next=2;return _regenerator.default.awrap(getImageSize(uri));case 2:_await$getImageSize=_context3.sent;photoW=_await$getImageSize.width;photoH=_await$getImageSize.height;crop=computeCropRegion(photoW,photoH,frameW,frameH);console.log('CCCD crop region',{photoW:photoW,photoH:photoH,frameW:frameW,frameH:frameH,crop:crop});_context3.next=3;return _regenerator.default.awrap(_reactNativeImageManipulator.default.manipulate(uri,[{crop:crop}],{compress:0.9,format:'jpeg'}));case 3:result=_context3.sent;return _context3.abrupt("return",result.uri);case 4:case"end":return _context3.stop();}},null,null,null,Promise);};var takePicture=function _callee4(){var photo,uri,cropped,_t2;return _regenerator.default.async(function(_context4){while(1)switch(_context4.prev=_context4.next){case 0:if(!(cameraRef.current==null||taking)){_context4.next=1;break;}return _context4.abrupt("return");case 1:setTaking(true);_context4.prev=2;_context4.next=3;return _regenerator.default.awrap(cameraRef.current.takePhoto({qualityPrioritization:'quality'}));case 3:photo=_context4.sent;uri='file://'+photo.path;_context4.next=4;return _regenerator.default.awrap(cropToFrame(uri));case 4:cropped=_context4.sent;setCaptured(cropped);_context4.next=6;break;case 5:_context4.prev=5;_t2=_context4["catch"](2);console.error(_t2);_AlertGlobal.AlertGlobal.show({type:'warning',title:'Thông báo',message:'Không chụp được ảnh. Vui lòng thử lại.'});case 6:_context4.prev=6;setTaking(false);return _context4.finish(6);case 7:case"end":return _context4.stop();}},null,null,[[2,5,6,7]],Promise);};var pickImageFromGallery=function _callee5(){var _result$assets,_result$assets$,result;return _regenerator.default.async(function(_context5){while(1)switch(_context5.prev=_context5.next){case 0:if(!(_reactNative.Platform.OS==='android')){_context5.next=2;break;}_context5.next=1;return _regenerator.default.awrap((0,_reactNativeImagePicker.launchImageLibrary)({mediaType:'photo'}));case 1:result=_context5.sent;if((_result$assets=result.assets)!=null&&(_result$assets$=_result$assets[0])!=null&&_result$assets$.uri){setCaptured(result.assets[0].uri);}_context5.next=3;break;case 2:_reactNativeImageCropPicker.default.openPicker({}).then(function(image){if(image!=null&&image.path){setCaptured('file://'+image.path);}});case 3:case"end":return _context5.stop();}},null,null,null,Promise);};var usePhoto=function usePhoto(){var _props$route$params$o,_props$route4,_props$route4$params,_props$route5,_props$route5$params;var callback=(_props$route$params$o=(_props$route4=props.route)==null?void 0:(_props$route4$params=_props$route4.params)==null?void 0:_props$route4$params.onCaptured)!=null?_props$route$params$o:(_props$route5=props.route)==null?void 0:(_props$route5$params=_props$route5.params)==null?void 0:_props$route5$params.callback;if(callback){navigation.goBack();callback(captured);}else{_AlertGlobal.AlertGlobal.show({type:'success',title:'Đã chụp ảnh',message:captured});}};return _react.default.createElement(_reactNative.View,{style:styles.container,__self:_this,__source:{fileName:_jsxFileName,lineNumber:253,columnNumber:5}},_react.default.createElement(_reactNative.View,{style:[styles.header,{paddingTop:insets.top+16}],__self:_this,__source:{fileName:_jsxFileName,lineNumber:254,columnNumber:7}},_react.default.createElement(_reactNative.TouchableOpacity,{onPress:function onPress(){navigation.goBack();},hitSlop:{top:12,bottom:12,left:12,right:12},__self:_this,__source:{fileName:_jsxFileName,lineNumber:255,columnNumber:9}},_react.default.createElement(_icons.IconBack,{__self:_this,__source:{fileName:_jsxFileName,lineNumber:261,columnNumber:11}})),_react.default.createElement(_reactNative.View,{style:styles.tabs,__self:_this,__source:{fileName:_jsxFileName,lineNumber:263,columnNumber:9}},_react.default.createElement(_reactNative.TouchableOpacity,{style:[styles.tab,mode==='photo'?styles.tabActive:styles.tabInactive],onPress:function onPress(){return setMode('photo');},__self:_this,__source:{fileName:_jsxFileName,lineNumber:264,columnNumber:11}},_react.default.createElement(_MText.MText,{style:mode==='photo'?styles.tabTextActive:styles.tabTextInactive,__self:_this,__source:{fileName:_jsxFileName,lineNumber:271,columnNumber:13}},"Ch\u1EE5p CCCD")),_react.default.createElement(_reactNative.TouchableOpacity,{style:[styles.tab,mode==='nfc'?styles.tabActive:styles.tabInactive],onPress:switchToNfc,__self:_this,__source:{fileName:_jsxFileName,lineNumber:279,columnNumber:11}},_react.default.createElement(_MText.MText,{style:mode==='nfc'?styles.tabTextActive:styles.tabTextInactive,__self:_this,__source:{fileName:_jsxFileName,lineNumber:286,columnNumber:13}},"\u0110\u1ECDc NFC")))),mode==='nfc'?_react.default.createElement(_react.default.Fragment,null,_react.default.createElement(_MText.MText,{style:styles.instruction,__self:_this,__source:{fileName:_jsxFileName,lineNumber:299,columnNumber:11}},"D\xF9ng camera \u0111\u1EC3 qu\xE9t chu\u1ED7i k\xFD t\u1EF1 m\u1EB7t sau th\u1EBB",'\n',"CCCD g\u1EAFn ch\xEDp \u1EDF ph\xEDa d\u01B0\u1EDBi"),_react.default.createElement(_reactNative.View,{style:styles.cameraWrap,__self:_this,__source:{fileName:_jsxFileName,lineNumber:304,columnNumber:11}},hasPermission&&cameraReady&&isFocused&&_react.default.createElement(_reactNativeMrzScanner.MrzScannerView,{ref:scannerRef,style:_reactNative.StyleSheet.absoluteFill,autoCapture:autoCapture,onMrzDetected:handleDetected,onError:handleError,__self:_this,__source:{fileName:_jsxFileName,lineNumber:306,columnNumber:15}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerTL],__self:_this,__source:{fileName:_jsxFileName,lineNumber:314,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerTR],__self:_this,__source:{fileName:_jsxFileName,lineNumber:315,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerBL],__self:_this,__source:{fileName:_jsxFileName,lineNumber:316,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerBR],__self:_this,__source:{fileName:_jsxFileName,lineNumber:317,columnNumber:13}})),_react.default.createElement(_reactNative.TouchableOpacity,{style:styles.checkboxRow,onPress:function onPress(){return setAutoCapture(function(v){return!v;});},activeOpacity:0.7,__self:_this,__source:{fileName:_jsxFileName,lineNumber:320,columnNumber:11}},_react.default.createElement(_reactNative.Text,{style:styles.checkboxLabel,__self:_this,__source:{fileName:_jsxFileName,lineNumber:325,columnNumber:13}},"Ch\u1EE5p t\u1EF1 \u0111\u1ED9ng"),_react.default.createElement(_reactNative.View,{style:[styles.checkbox,autoCapture&&styles.checkboxChecked],__self:_this,__source:{fileName:_jsxFileName,lineNumber:326,columnNumber:13}},autoCapture&&_react.default.createElement(_reactNative.Text,{style:styles.checkmark,__self:_this,__source:{fileName:_jsxFileName,lineNumber:329,columnNumber:31}},"\u2713"))),_react.default.createElement(_MText.MText,{style:styles.hint,__self:_this,__source:{fileName:_jsxFileName,lineNumber:333,columnNumber:11}},"Vui l\xF2ng \u0111\u1EB7t chu\u1ED7i k\xED t\u1EF1 n\u1EB1m v\u1EEBa khung",'\n',"h\xECnh ch\u1EEF nh\u1EADt, ch\u1EE5p \u0111\u1EE7 \xE1nh s\xE1ng v\xE0 r\xF5 n\xE9t.")):mode==='photo'?_react.default.createElement(_react.default.Fragment,null,_react.default.createElement(_MText.MText,{style:styles.instruction,__self:_this,__source:{fileName:_jsxFileName,lineNumber:340,columnNumber:11}},front?'Chụp CMND/ CCCD mặt trước':'Chụp CMND/ CCCD mặt sau'),_react.default.createElement(_reactNative.View,{style:styles.photoFrame,onLayout:function onLayout(e){frameLayout.current=e.nativeEvent.layout;},__self:_this,__source:{fileName:_jsxFileName,lineNumber:344,columnNumber:11}},captured?_react.default.createElement(_reactNative.Image,{source:{uri:captured},style:_reactNative.StyleSheet.absoluteFill,resizeMode:"cover",__self:_this,__source:{fileName:_jsxFileName,lineNumber:351,columnNumber:15}}):hasPermission&&cameraReady&&device?_react.default.createElement(_reactNativeVisionCamera.Camera,{ref:cameraRef,style:_reactNative.StyleSheet.absoluteFill,device:device,isActive:isFocused&&mode==='photo'&&!captured,photo:true,format:format,__self:_this,__source:{fileName:_jsxFileName,lineNumber:357,columnNumber:15}}):null,_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerTL],__self:_this,__source:{fileName:_jsxFileName,lineNumber:366,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerTR],__self:_this,__source:{fileName:_jsxFileName,lineNumber:367,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerBL],__self:_this,__source:{fileName:_jsxFileName,lineNumber:368,columnNumber:13}}),_react.default.createElement(_reactNative.View,{style:[styles.corner,styles.cornerBR],__self:_this,__source:{fileName:_jsxFileName,lineNumber:369,columnNumber:13}})),captured?_react.default.createElement(_reactNative.View,{style:styles.previewActions,__self:_this,__source:{fileName:_jsxFileName,lineNumber:373,columnNumber:13}},_react.default.createElement(_reactNative.TouchableOpacity,{style:styles.retakeButton,onPress:function onPress(){return setCaptured(null);},__self:_this,__source:{fileName:_jsxFileName,lineNumber:374,columnNumber:15}},_react.default.createElement(_MText.MText,{style:styles.retakeText,__self:_this,__source:{fileName:_jsxFileName,lineNumber:378,columnNumber:17}},"Ch\u1EE5p l\u1EA1i")),_react.default.createElement(_reactNative.TouchableOpacity,{style:styles.useButton,onPress:usePhoto,__self:_this,__source:{fileName:_jsxFileName,lineNumber:380,columnNumber:15}},_react.default.createElement(_MText.MText,{style:styles.useText,__self:_this,__source:{fileName:_jsxFileName,lineNumber:381,columnNumber:17}},"D\xF9ng \u1EA3nh n\xE0y"))):_react.default.createElement(_react.default.Fragment,null,_react.default.createElement(_reactNative.TouchableOpacity,{style:styles.galleryButton,onPress:pickImageFromGallery,__self:_this,__source:{fileName:_jsxFileName,lineNumber:386,columnNumber:15}},_react.default.createElement(_svgs.IconUpload,{__self:_this,__source:{fileName:_jsxFileName,lineNumber:390,columnNumber:17}}),_react.default.createElement(_MText.MText,{style:styles.galleryText,__self:_this,__source:{fileName:_jsxFileName,lineNumber:391,columnNumber:17}},"T\u1EA3i \u1EA3nh t\u1EEB th\u01B0 vi\u1EC7n")),_react.default.createElement(_reactNative.View,{style:styles.spacer,__self:_this,__source:{fileName:_jsxFileName,lineNumber:393,columnNumber:15}}),_react.default.createElement(_MText.MText,{style:[styles.hint,{marginTop:0,marginBottom:20}],__self:_this,__source:{fileName:_jsxFileName,lineNumber:394,columnNumber:15}},"Vui l\xF2ng \u0111\u1EB7t gi\u1EA5y t\u1EDD n\u1EB1m v\u1EEBa khung h\xECnh ch\u1EEF nh\u1EADt,",'\n',"ch\u1EE5p \u0111\u1EE7 \xE1nh s\xE1ng v\xE0 r\xF5 n\xE9t."),_react.default.createElement(_reactNative.TouchableOpacity,{style:[styles.captureButton,{marginBottom:insets.bottom+24}],onPress:takePicture,disabled:taking,__self:_this,__source:{fileName:_jsxFileName,lineNumber:398,columnNumber:15}},_react.default.createElement(_icons.TakePhotoSvg,{__self:_this,__source:{fileName:_jsxFileName,lineNumber:406,columnNumber:17}})))):null);};var _default=exports.default=(0,_mobxReactLite.observer)(ScanMRZ);var styles=_reactNative.StyleSheet.create({container:{flex:1,backgroundColor:'#FFFFFF'},header:{flexDirection:'row',alignItems:'center',paddingHorizontal:16,paddingTop:8},tabs:{flex:1,flexDirection:'row',justifyContent:'center',gap:8},tab:{paddingHorizontal:14,paddingVertical:7,borderRadius:8},tabInactive:{backgroundColor:'#F1F1F1'},tabActive:{backgroundColor:ORANGE},tabTextInactive:{color:'#555555',fontSize:13,fontWeight:'500'},tabTextActive:{color:'#FFFFFF',fontSize:13,fontWeight:'600'},instruction:{textAlign:'center',color:'#333333',fontSize:14,lineHeight:20,marginTop:20,marginBottom:28},cameraWrap:{marginHorizontal:16,height:220,borderRadius:8,overflow:'hidden',backgroundColor:'#000000'},photoFrame:{marginHorizontal:24,aspectRatio:CARD_ASPECT_RATIO,borderRadius:8,overflow:'hidden',backgroundColor:'#000000'},corner:{position:'absolute',width:26,height:26,borderColor:ORANGE},cornerTL:{top:0,left:0,borderTopWidth:4,borderLeftWidth:4,borderTopLeftRadius:8},cornerTR:{top:0,right:0,borderTopWidth:4,borderRightWidth:4,borderTopRightRadius:8},cornerBL:{bottom:0,left:0,borderBottomWidth:4,borderLeftWidth:4,borderBottomLeftRadius:8},cornerBR:{bottom:0,right:0,borderBottomWidth:4,borderRightWidth:4,borderBottomRightRadius:8},checkboxRow:{flexDirection:'row',alignItems:'center',justifyContent:'center',marginTop:24,gap:8},checkboxLabel:{fontSize:14,color:'#333333'},checkbox:{width:20,height:20,borderRadius:4,borderWidth:1.5,borderColor:'#BBBBBB',alignItems:'center',justifyContent:'center'},checkboxChecked:{backgroundColor:'#22A45D',borderColor:'#22A45D'},checkmark:{color:'#FFFFFF',fontSize:13,fontWeight:'700'},hint:{textAlign:'center',color:'#555555',fontSize:13,lineHeight:19,marginTop:48},galleryButton:{alignSelf:'center',flexDirection:'row',alignItems:'center',gap:8,marginTop:20,paddingHorizontal:14,paddingVertical:8,borderRadius:8,borderWidth:1,borderColor:ORANGE,backgroundColor:'#FFFFFF'},galleryText:{color:'#333333',fontSize:13},spacer:{flex:1},captureButton:{alignSelf:'center'},previewActions:{flexDirection:'row',gap:12,marginTop:24,marginHorizontal:24},retakeButton:{flex:1,height:40,borderRadius:20,borderWidth:1,borderColor:ORANGE,alignItems:'center',justifyContent:'center'},retakeText:{color:ORANGE,fontSize:14,fontWeight:'600'},useButton:{flex:1,height:40,borderRadius:20,backgroundColor:ORANGE,alignItems:'center',justifyContent:'center'},useText:{color:'#FFFFFF',fontSize:14,fontWeight:'600'}});
|
|
2
|
+
//# sourceMappingURL=ScanMRZ.js.map
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
Image,
|
|
7
|
+
Linking,
|
|
8
|
+
NativeModules,
|
|
9
|
+
PermissionsAndroid,
|
|
10
|
+
Platform,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
Text,
|
|
13
|
+
TouchableOpacity,
|
|
14
|
+
View,
|
|
15
|
+
} from 'react-native';
|
|
16
|
+
import {
|
|
17
|
+
MrzScannerView,
|
|
18
|
+
type MrzError,
|
|
19
|
+
type MrzResult,
|
|
20
|
+
type MrzScannerHandle,
|
|
21
|
+
} from 'react-native-mrz-scanner';
|
|
22
|
+
import {
|
|
23
|
+
Camera as VisionCamera,
|
|
24
|
+
useCameraDevice,
|
|
25
|
+
useCameraFormat,
|
|
26
|
+
} from 'react-native-vision-camera';
|
|
27
|
+
import RNImageManipulator from '@oguzhnatly/react-native-image-manipulator';
|
|
28
|
+
import ImagePicker from 'react-native-image-crop-picker';
|
|
29
|
+
import { launchImageLibrary } from 'react-native-image-picker';
|
|
30
|
+
import { AlertGlobal } from '../../components/ModalNotification/AlertGlobal';
|
|
31
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
32
|
+
import { useIsFocused, useNavigation } from '@react-navigation/native';
|
|
33
|
+
import { IconBack, TakePhotoSvg } from '../../assets/icons';
|
|
34
|
+
import { IconUpload } from '../../assets/svgs';
|
|
35
|
+
import { MText } from '../../components/MText';
|
|
36
|
+
|
|
37
|
+
const { NFCReader } = NativeModules;
|
|
38
|
+
|
|
39
|
+
const ORANGE = '#F05123';
|
|
40
|
+
// Tỉ lệ thẻ CCCD/CMND theo chuẩn ID-1: 85.6mm x 54mm
|
|
41
|
+
const CARD_ASPECT_RATIO = 85.6 / 54;
|
|
42
|
+
|
|
43
|
+
// iPhone 7 trở lên (iOS 13+) đều có NFC và iOS không cho tắt NFC nên iOS
|
|
44
|
+
// mặc định là hỗ trợ + đang bật. Android hỏi native module; máy không có
|
|
45
|
+
// phần cứng NFC (hoặc app chủ chưa implement getNfcStatus) trả về false.
|
|
46
|
+
const getNfcStatus = async () => {
|
|
47
|
+
if (Platform.OS === 'ios') return { supported: true, enabled: true };
|
|
48
|
+
try {
|
|
49
|
+
const status = await NFCReader?.getNfcStatus?.();
|
|
50
|
+
return { supported: !!status?.supported, enabled: !!status?.enabled };
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return { supported: false, enabled: false };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const promptEnableNfc = () => {
|
|
57
|
+
AlertGlobal.show({
|
|
58
|
+
type: 'warning',
|
|
59
|
+
title: 'NFC đang tắt',
|
|
60
|
+
message:
|
|
61
|
+
'Thiết bị của bạn hỗ trợ NFC nhưng đang tắt. Vui lòng bật NFC để đọc thông tin thẻ CCCD.',
|
|
62
|
+
primaryText: 'Mở cài đặt',
|
|
63
|
+
secondaryText: 'Để sau',
|
|
64
|
+
onPrimaryPress: () => {
|
|
65
|
+
Linking.sendIntent('android.settings.NFC_SETTINGS').catch(() => {});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const getImageSize = (uri) =>
|
|
71
|
+
new Promise((resolve, reject) => {
|
|
72
|
+
Image.getSize(uri, (width, height) => resolve({ width, height }), reject);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Tính vùng crop (pixel trên ảnh gốc) tương ứng với phần preview nhìn thấy
|
|
77
|
+
* trong khung. VisionCamera fill khung theo kiểu "cover": ảnh được scale để
|
|
78
|
+
* phủ kín khung rồi cắt phần thừa, nên vùng nhìn thấy là hình chữ nhật đồng
|
|
79
|
+
* dạng với khung và nằm chính giữa ảnh.
|
|
80
|
+
*/
|
|
81
|
+
export const computeCropRegion = (photoW, photoH, frameW, frameH) => {
|
|
82
|
+
// Ảnh lưu ngang nhưng khung dọc (hoặc ngược lại) -> tính trên hệ trục đã xoay 90°
|
|
83
|
+
const rotated = photoW > photoH !== frameW > frameH;
|
|
84
|
+
const w = rotated ? photoH : photoW;
|
|
85
|
+
const h = rotated ? photoW : photoH;
|
|
86
|
+
|
|
87
|
+
const scale = Math.max(frameW / w, frameH / h);
|
|
88
|
+
const cropW = Math.min(w, Math.round(frameW / scale));
|
|
89
|
+
const cropH = Math.min(h, Math.round(frameH / scale));
|
|
90
|
+
const cropX = Math.max(0, Math.round((w - cropW) / 2));
|
|
91
|
+
const cropY = Math.max(0, Math.round((h - cropH) / 2));
|
|
92
|
+
|
|
93
|
+
// Vùng crop luôn nằm chính giữa nên khi ảnh xoay 90° chỉ cần đảo trục
|
|
94
|
+
return rotated
|
|
95
|
+
? { originX: cropY, originY: cropX, width: cropH, height: cropW }
|
|
96
|
+
: { originX: cropX, originY: cropY, width: cropW, height: cropH };
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const ScanMRZ = (props: any) => {
|
|
100
|
+
const insets = useSafeAreaInsets();
|
|
101
|
+
const navigation = useNavigation();
|
|
102
|
+
const isFocused = useIsFocused();
|
|
103
|
+
const scannerRef = useRef<MrzScannerHandle>(null);
|
|
104
|
+
const cameraRef = useRef(null);
|
|
105
|
+
const frameLayout = useRef({ width: 0, height: 0 });
|
|
106
|
+
const [mode, setMode] = useState(props.route?.params?.initialMode ?? null);
|
|
107
|
+
const [autoCapture, setAutoCapture] = useState(true);
|
|
108
|
+
const [hasPermission, setHasPermission] = useState(Platform.OS === 'ios');
|
|
109
|
+
const [cameraReady, setCameraReady] = useState(false);
|
|
110
|
+
const [taking, setTaking] = useState(false);
|
|
111
|
+
const [captured, setCaptured] = useState(null);
|
|
112
|
+
|
|
113
|
+
const device = useCameraDevice('back');
|
|
114
|
+
const format = useCameraFormat(device, [
|
|
115
|
+
{ photoResolution: { width: 1280, height: 720 } },
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
const front = props.route?.params?.front !== false;
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (Platform.OS === 'android') {
|
|
122
|
+
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
|
|
123
|
+
title: 'Quyền camera',
|
|
124
|
+
message:
|
|
125
|
+
'Ứng dụng cần camera để quét chuỗi ký tự trên mặt sau thẻ CCCD.',
|
|
126
|
+
buttonPositive: 'Đồng ý',
|
|
127
|
+
buttonNegative: 'Từ chối',
|
|
128
|
+
}).then((status) =>
|
|
129
|
+
setHasPermission(status === PermissionsAndroid.RESULTS.GRANTED)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
// Chưa chỉ định initialMode: thiết bị hỗ trợ NFC thì ưu tiên tab Đọc NFC,
|
|
135
|
+
// không hỗ trợ thì vào thẳng tab Chụp CCCD. Có NFC nhưng đang tắt thì vẫn
|
|
136
|
+
// vào tab NFC kèm nhắc bật NFC trong cài đặt.
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (props.route?.params?.initialMode) return;
|
|
139
|
+
getNfcStatus().then(({ supported, enabled }) => {
|
|
140
|
+
setMode(supported ? 'nfc' : 'photo');
|
|
141
|
+
if (supported && !enabled) {
|
|
142
|
+
promptEnableNfc();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
// Bấm tab Đọc NFC: check lại trạng thái vì người dùng có thể vừa tắt/bật NFC
|
|
149
|
+
const switchToNfc = async () => {
|
|
150
|
+
setMode('nfc');
|
|
151
|
+
const { supported, enabled } = await getNfcStatus();
|
|
152
|
+
if (supported && !enabled) {
|
|
153
|
+
promptEnableNfc();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Đổi chế độ: gỡ camera cũ trước, chờ Android nhả camera rồi mới gắn camera mới
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
setCameraReady(false);
|
|
160
|
+
const timeout = setTimeout(() => setCameraReady(true), 500);
|
|
161
|
+
return () => clearTimeout(timeout);
|
|
162
|
+
}, [mode]);
|
|
163
|
+
|
|
164
|
+
const handleDetected = useCallback((result: MrzResult) => {
|
|
165
|
+
Alert.alert(
|
|
166
|
+
'Đã đọc MRZ',
|
|
167
|
+
[
|
|
168
|
+
`Họ tên: ${result.fullName}`,
|
|
169
|
+
`Số định danh: ${result.idNumber ?? result.documentNumber}`,
|
|
170
|
+
`Ngày sinh: ${result.dateOfBirth}`,
|
|
171
|
+
`Hết hạn: ${result.dateOfExpiry}`,
|
|
172
|
+
`Hợp lệ: ${result.valid ? 'Có' : 'Không'}`,
|
|
173
|
+
].join('\n'),
|
|
174
|
+
[{ text: 'Quét lại', onPress: () => scannerRef.current?.resume() }]
|
|
175
|
+
);
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const handleError = useCallback((error: MrzError) => {
|
|
179
|
+
AlertGlobal.show({
|
|
180
|
+
type: 'warning',
|
|
181
|
+
title: 'Thông báo',
|
|
182
|
+
message: error?.message,
|
|
183
|
+
});
|
|
184
|
+
}, []);
|
|
185
|
+
|
|
186
|
+
const cropToFrame = async (uri) => {
|
|
187
|
+
const { width: frameW, height: frameH } = frameLayout.current;
|
|
188
|
+
if (!frameW || !frameH) return uri;
|
|
189
|
+
const { width: photoW, height: photoH } = await getImageSize(uri);
|
|
190
|
+
const crop = computeCropRegion(photoW, photoH, frameW, frameH);
|
|
191
|
+
console.log('CCCD crop region', { photoW, photoH, frameW, frameH, crop });
|
|
192
|
+
const result = await RNImageManipulator.manipulate(uri, [{ crop }], {
|
|
193
|
+
compress: 0.9,
|
|
194
|
+
format: 'jpeg',
|
|
195
|
+
});
|
|
196
|
+
return result.uri;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const takePicture = async () => {
|
|
200
|
+
if (cameraRef.current == null || taking) return;
|
|
201
|
+
setTaking(true);
|
|
202
|
+
try {
|
|
203
|
+
const photo = await cameraRef.current.takePhoto({
|
|
204
|
+
qualityPrioritization: 'quality',
|
|
205
|
+
});
|
|
206
|
+
const uri = 'file://' + photo.path;
|
|
207
|
+
const cropped = await cropToFrame(uri);
|
|
208
|
+
setCaptured(cropped);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error(err);
|
|
211
|
+
AlertGlobal.show({
|
|
212
|
+
type: 'warning',
|
|
213
|
+
title: 'Thông báo',
|
|
214
|
+
message: 'Không chụp được ảnh. Vui lòng thử lại.',
|
|
215
|
+
});
|
|
216
|
+
} finally {
|
|
217
|
+
setTaking(false);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Ảnh từ thư viện không đi qua preview nên không có toạ độ khung để crop
|
|
222
|
+
const pickImageFromGallery = async () => {
|
|
223
|
+
if (Platform.OS === 'android') {
|
|
224
|
+
const result = await launchImageLibrary({ mediaType: 'photo' });
|
|
225
|
+
if (result.assets?.[0]?.uri) {
|
|
226
|
+
setCaptured(result.assets[0].uri);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
ImagePicker.openPicker({}).then((image) => {
|
|
230
|
+
if (image?.path) {
|
|
231
|
+
setCaptured('file://' + image.path);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const usePhoto = () => {
|
|
238
|
+
const callback =
|
|
239
|
+
props.route?.params?.onCaptured ?? props.route?.params?.callback;
|
|
240
|
+
if (callback) {
|
|
241
|
+
navigation.goBack();
|
|
242
|
+
callback(captured);
|
|
243
|
+
} else {
|
|
244
|
+
AlertGlobal.show({
|
|
245
|
+
type: 'success',
|
|
246
|
+
title: 'Đã chụp ảnh',
|
|
247
|
+
message: captured,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<View style={styles.container}>
|
|
254
|
+
<View style={[styles.header, { paddingTop: insets.top + 16 }]}>
|
|
255
|
+
<TouchableOpacity
|
|
256
|
+
onPress={() => {
|
|
257
|
+
navigation.goBack();
|
|
258
|
+
}}
|
|
259
|
+
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
|
|
260
|
+
>
|
|
261
|
+
<IconBack />
|
|
262
|
+
</TouchableOpacity>
|
|
263
|
+
<View style={styles.tabs}>
|
|
264
|
+
<TouchableOpacity
|
|
265
|
+
style={[
|
|
266
|
+
styles.tab,
|
|
267
|
+
mode === 'photo' ? styles.tabActive : styles.tabInactive,
|
|
268
|
+
]}
|
|
269
|
+
onPress={() => setMode('photo')}
|
|
270
|
+
>
|
|
271
|
+
<MText
|
|
272
|
+
style={
|
|
273
|
+
mode === 'photo' ? styles.tabTextActive : styles.tabTextInactive
|
|
274
|
+
}
|
|
275
|
+
>
|
|
276
|
+
Chụp CCCD
|
|
277
|
+
</MText>
|
|
278
|
+
</TouchableOpacity>
|
|
279
|
+
<TouchableOpacity
|
|
280
|
+
style={[
|
|
281
|
+
styles.tab,
|
|
282
|
+
mode === 'nfc' ? styles.tabActive : styles.tabInactive,
|
|
283
|
+
]}
|
|
284
|
+
onPress={switchToNfc}
|
|
285
|
+
>
|
|
286
|
+
<MText
|
|
287
|
+
style={
|
|
288
|
+
mode === 'nfc' ? styles.tabTextActive : styles.tabTextInactive
|
|
289
|
+
}
|
|
290
|
+
>
|
|
291
|
+
Đọc NFC
|
|
292
|
+
</MText>
|
|
293
|
+
</TouchableOpacity>
|
|
294
|
+
</View>
|
|
295
|
+
</View>
|
|
296
|
+
|
|
297
|
+
{mode === 'nfc' ? (
|
|
298
|
+
<>
|
|
299
|
+
<MText style={styles.instruction}>
|
|
300
|
+
Dùng camera để quét chuỗi ký tự mặt sau thẻ{'\n'}CCCD gắn chíp ở
|
|
301
|
+
phía dưới
|
|
302
|
+
</MText>
|
|
303
|
+
{/* Khung camera + 4 góc cam */}
|
|
304
|
+
<View style={styles.cameraWrap}>
|
|
305
|
+
{hasPermission && cameraReady && isFocused && (
|
|
306
|
+
<MrzScannerView
|
|
307
|
+
ref={scannerRef}
|
|
308
|
+
style={StyleSheet.absoluteFill}
|
|
309
|
+
autoCapture={autoCapture}
|
|
310
|
+
onMrzDetected={handleDetected}
|
|
311
|
+
onError={handleError}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
314
|
+
<View style={[styles.corner, styles.cornerTL]} />
|
|
315
|
+
<View style={[styles.corner, styles.cornerTR]} />
|
|
316
|
+
<View style={[styles.corner, styles.cornerBL]} />
|
|
317
|
+
<View style={[styles.corner, styles.cornerBR]} />
|
|
318
|
+
</View>
|
|
319
|
+
{/* Checkbox chụp tự động */}
|
|
320
|
+
<TouchableOpacity
|
|
321
|
+
style={styles.checkboxRow}
|
|
322
|
+
onPress={() => setAutoCapture((v) => !v)}
|
|
323
|
+
activeOpacity={0.7}
|
|
324
|
+
>
|
|
325
|
+
<Text style={styles.checkboxLabel}>Chụp tự động</Text>
|
|
326
|
+
<View
|
|
327
|
+
style={[styles.checkbox, autoCapture && styles.checkboxChecked]}
|
|
328
|
+
>
|
|
329
|
+
{autoCapture && <Text style={styles.checkmark}>✓</Text>}
|
|
330
|
+
</View>
|
|
331
|
+
</TouchableOpacity>
|
|
332
|
+
|
|
333
|
+
<MText style={styles.hint}>
|
|
334
|
+
Vui lòng đặt chuỗi kí tự nằm vừa khung{'\n'}hình chữ nhật, chụp đủ
|
|
335
|
+
ánh sáng và rõ nét.
|
|
336
|
+
</MText>
|
|
337
|
+
</>
|
|
338
|
+
) : mode === 'photo' ? (
|
|
339
|
+
<>
|
|
340
|
+
<MText style={styles.instruction}>
|
|
341
|
+
{front ? 'Chụp CMND/ CCCD mặt trước' : 'Chụp CMND/ CCCD mặt sau'}
|
|
342
|
+
</MText>
|
|
343
|
+
{/* Khung tỉ lệ thẻ CCCD: vùng nhìn thấy của preview = vùng sẽ crop */}
|
|
344
|
+
<View
|
|
345
|
+
style={styles.photoFrame}
|
|
346
|
+
onLayout={(e) => {
|
|
347
|
+
frameLayout.current = e.nativeEvent.layout;
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
{captured ? (
|
|
351
|
+
<Image
|
|
352
|
+
source={{ uri: captured }}
|
|
353
|
+
style={StyleSheet.absoluteFill}
|
|
354
|
+
resizeMode="cover"
|
|
355
|
+
/>
|
|
356
|
+
) : hasPermission && cameraReady && device ? (
|
|
357
|
+
<VisionCamera
|
|
358
|
+
ref={cameraRef}
|
|
359
|
+
style={StyleSheet.absoluteFill}
|
|
360
|
+
device={device}
|
|
361
|
+
isActive={isFocused && mode === 'photo' && !captured}
|
|
362
|
+
photo={true}
|
|
363
|
+
format={format}
|
|
364
|
+
/>
|
|
365
|
+
) : null}
|
|
366
|
+
<View style={[styles.corner, styles.cornerTL]} />
|
|
367
|
+
<View style={[styles.corner, styles.cornerTR]} />
|
|
368
|
+
<View style={[styles.corner, styles.cornerBL]} />
|
|
369
|
+
<View style={[styles.corner, styles.cornerBR]} />
|
|
370
|
+
</View>
|
|
371
|
+
|
|
372
|
+
{captured ? (
|
|
373
|
+
<View style={styles.previewActions}>
|
|
374
|
+
<TouchableOpacity
|
|
375
|
+
style={styles.retakeButton}
|
|
376
|
+
onPress={() => setCaptured(null)}
|
|
377
|
+
>
|
|
378
|
+
<MText style={styles.retakeText}>Chụp lại</MText>
|
|
379
|
+
</TouchableOpacity>
|
|
380
|
+
<TouchableOpacity style={styles.useButton} onPress={usePhoto}>
|
|
381
|
+
<MText style={styles.useText}>Dùng ảnh này</MText>
|
|
382
|
+
</TouchableOpacity>
|
|
383
|
+
</View>
|
|
384
|
+
) : (
|
|
385
|
+
<>
|
|
386
|
+
<TouchableOpacity
|
|
387
|
+
style={styles.galleryButton}
|
|
388
|
+
onPress={pickImageFromGallery}
|
|
389
|
+
>
|
|
390
|
+
<IconUpload />
|
|
391
|
+
<MText style={styles.galleryText}>Tải ảnh từ thư viện</MText>
|
|
392
|
+
</TouchableOpacity>
|
|
393
|
+
<View style={styles.spacer} />
|
|
394
|
+
<MText style={[styles.hint, { marginTop: 0, marginBottom: 20 }]}>
|
|
395
|
+
Vui lòng đặt giấy tờ nằm vừa khung hình chữ nhật,{'\n'}chụp đủ
|
|
396
|
+
ánh sáng và rõ nét.
|
|
397
|
+
</MText>
|
|
398
|
+
<TouchableOpacity
|
|
399
|
+
style={[
|
|
400
|
+
styles.captureButton,
|
|
401
|
+
{ marginBottom: insets.bottom + 24 },
|
|
402
|
+
]}
|
|
403
|
+
onPress={takePicture}
|
|
404
|
+
disabled={taking}
|
|
405
|
+
>
|
|
406
|
+
<TakePhotoSvg />
|
|
407
|
+
</TouchableOpacity>
|
|
408
|
+
</>
|
|
409
|
+
)}
|
|
410
|
+
</>
|
|
411
|
+
) : null}
|
|
412
|
+
</View>
|
|
413
|
+
);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
export default observer(ScanMRZ);
|
|
417
|
+
const styles = StyleSheet.create({
|
|
418
|
+
container: { flex: 1, backgroundColor: '#FFFFFF' },
|
|
419
|
+
header: {
|
|
420
|
+
flexDirection: 'row',
|
|
421
|
+
alignItems: 'center',
|
|
422
|
+
paddingHorizontal: 16,
|
|
423
|
+
paddingTop: 8,
|
|
424
|
+
},
|
|
425
|
+
tabs: {
|
|
426
|
+
flex: 1,
|
|
427
|
+
flexDirection: 'row',
|
|
428
|
+
justifyContent: 'center',
|
|
429
|
+
gap: 8,
|
|
430
|
+
},
|
|
431
|
+
tab: {
|
|
432
|
+
paddingHorizontal: 14,
|
|
433
|
+
paddingVertical: 7,
|
|
434
|
+
borderRadius: 8,
|
|
435
|
+
},
|
|
436
|
+
tabInactive: { backgroundColor: '#F1F1F1' },
|
|
437
|
+
tabActive: { backgroundColor: ORANGE },
|
|
438
|
+
tabTextInactive: { color: '#555555', fontSize: 13, fontWeight: '500' },
|
|
439
|
+
tabTextActive: { color: '#FFFFFF', fontSize: 13, fontWeight: '600' },
|
|
440
|
+
instruction: {
|
|
441
|
+
textAlign: 'center',
|
|
442
|
+
color: '#333333',
|
|
443
|
+
fontSize: 14,
|
|
444
|
+
lineHeight: 20,
|
|
445
|
+
marginTop: 20,
|
|
446
|
+
marginBottom: 28,
|
|
447
|
+
},
|
|
448
|
+
cameraWrap: {
|
|
449
|
+
marginHorizontal: 16,
|
|
450
|
+
height: 220,
|
|
451
|
+
borderRadius: 8,
|
|
452
|
+
overflow: 'hidden',
|
|
453
|
+
backgroundColor: '#000000',
|
|
454
|
+
},
|
|
455
|
+
photoFrame: {
|
|
456
|
+
marginHorizontal: 24,
|
|
457
|
+
aspectRatio: CARD_ASPECT_RATIO,
|
|
458
|
+
borderRadius: 8,
|
|
459
|
+
overflow: 'hidden',
|
|
460
|
+
backgroundColor: '#000000',
|
|
461
|
+
},
|
|
462
|
+
corner: {
|
|
463
|
+
position: 'absolute',
|
|
464
|
+
width: 26,
|
|
465
|
+
height: 26,
|
|
466
|
+
borderColor: ORANGE,
|
|
467
|
+
},
|
|
468
|
+
cornerTL: {
|
|
469
|
+
top: 0,
|
|
470
|
+
left: 0,
|
|
471
|
+
borderTopWidth: 4,
|
|
472
|
+
borderLeftWidth: 4,
|
|
473
|
+
borderTopLeftRadius: 8,
|
|
474
|
+
},
|
|
475
|
+
cornerTR: {
|
|
476
|
+
top: 0,
|
|
477
|
+
right: 0,
|
|
478
|
+
borderTopWidth: 4,
|
|
479
|
+
borderRightWidth: 4,
|
|
480
|
+
borderTopRightRadius: 8,
|
|
481
|
+
},
|
|
482
|
+
cornerBL: {
|
|
483
|
+
bottom: 0,
|
|
484
|
+
left: 0,
|
|
485
|
+
borderBottomWidth: 4,
|
|
486
|
+
borderLeftWidth: 4,
|
|
487
|
+
borderBottomLeftRadius: 8,
|
|
488
|
+
},
|
|
489
|
+
cornerBR: {
|
|
490
|
+
bottom: 0,
|
|
491
|
+
right: 0,
|
|
492
|
+
borderBottomWidth: 4,
|
|
493
|
+
borderRightWidth: 4,
|
|
494
|
+
borderBottomRightRadius: 8,
|
|
495
|
+
},
|
|
496
|
+
checkboxRow: {
|
|
497
|
+
flexDirection: 'row',
|
|
498
|
+
alignItems: 'center',
|
|
499
|
+
justifyContent: 'center',
|
|
500
|
+
marginTop: 24,
|
|
501
|
+
gap: 8,
|
|
502
|
+
},
|
|
503
|
+
checkboxLabel: { fontSize: 14, color: '#333333' },
|
|
504
|
+
checkbox: {
|
|
505
|
+
width: 20,
|
|
506
|
+
height: 20,
|
|
507
|
+
borderRadius: 4,
|
|
508
|
+
borderWidth: 1.5,
|
|
509
|
+
borderColor: '#BBBBBB',
|
|
510
|
+
alignItems: 'center',
|
|
511
|
+
justifyContent: 'center',
|
|
512
|
+
},
|
|
513
|
+
checkboxChecked: { backgroundColor: '#22A45D', borderColor: '#22A45D' },
|
|
514
|
+
checkmark: { color: '#FFFFFF', fontSize: 13, fontWeight: '700' },
|
|
515
|
+
hint: {
|
|
516
|
+
textAlign: 'center',
|
|
517
|
+
color: '#555555',
|
|
518
|
+
fontSize: 13,
|
|
519
|
+
lineHeight: 19,
|
|
520
|
+
marginTop: 48,
|
|
521
|
+
},
|
|
522
|
+
galleryButton: {
|
|
523
|
+
alignSelf: 'center',
|
|
524
|
+
flexDirection: 'row',
|
|
525
|
+
alignItems: 'center',
|
|
526
|
+
gap: 8,
|
|
527
|
+
marginTop: 20,
|
|
528
|
+
paddingHorizontal: 14,
|
|
529
|
+
paddingVertical: 8,
|
|
530
|
+
borderRadius: 8,
|
|
531
|
+
borderWidth: 1,
|
|
532
|
+
borderColor: ORANGE,
|
|
533
|
+
backgroundColor: '#FFFFFF',
|
|
534
|
+
},
|
|
535
|
+
galleryText: { color: '#333333', fontSize: 13 },
|
|
536
|
+
spacer: { flex: 1 },
|
|
537
|
+
captureButton: { alignSelf: 'center' },
|
|
538
|
+
previewActions: {
|
|
539
|
+
flexDirection: 'row',
|
|
540
|
+
gap: 12,
|
|
541
|
+
marginTop: 24,
|
|
542
|
+
marginHorizontal: 24,
|
|
543
|
+
},
|
|
544
|
+
retakeButton: {
|
|
545
|
+
flex: 1,
|
|
546
|
+
height: 40,
|
|
547
|
+
borderRadius: 20,
|
|
548
|
+
borderWidth: 1,
|
|
549
|
+
borderColor: ORANGE,
|
|
550
|
+
alignItems: 'center',
|
|
551
|
+
justifyContent: 'center',
|
|
552
|
+
},
|
|
553
|
+
retakeText: { color: ORANGE, fontSize: 14, fontWeight: '600' },
|
|
554
|
+
useButton: {
|
|
555
|
+
flex: 1,
|
|
556
|
+
height: 40,
|
|
557
|
+
borderRadius: 20,
|
|
558
|
+
backgroundColor: ORANGE,
|
|
559
|
+
alignItems: 'center',
|
|
560
|
+
justifyContent: 'center',
|
|
561
|
+
},
|
|
562
|
+
useText: { color: '#FFFFFF', fontSize: 14, fontWeight: '600' },
|
|
563
|
+
});
|