@sanctum-key/react-native-sdk 1.0.14 → 1.0.15
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/build/package.json +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +148 -8
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +42 -15
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/utils/cropByObb.d.ts +0 -12
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +73 -98
- package/build/src/utils/cropByObb.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +190 -33
- package/src/components/KYCElements/IDCardCapture.tsx +49 -15
- package/src/utils/cropByObb.ts +90 -127
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cropByObb.js","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,gBAAgB,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIlC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAY;IAC3C,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEzB,yDAAyD;IACzD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;QAChE,MAAM,KAAK,GAAI,KAAa,CAAC,UAAU,CAAC;QACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAClD,CAAC;IAED,qCAAqC;IACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,OAAY;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,yDAAyD;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAE5D,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,eAAe,IAAI,KAAK,EAAE,CAAC;QACnE,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;QACnD,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;QAE7F,yFAAyF;QACzF,IAAI,CAAC,aAAa,IAAI,eAAe,EAAE,CAAC;YACtC,IAAI,eAAe,EAAE,CAAC;gBACpB,OAAO,CAAC,IAAI,CAAC,6BAA6B,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9E,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,wEAAwE;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+BAA+B;IAC/B,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,eAAe,IAAI,OAAO,EAAE,CAAC;QACzE,MAAM,KAAK,GAAI,OAAe,CAAC,aAAa,CAAC;QAC7C,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gEAAgE;AAChE,SAAS,WAAW,CAAC,MAAe;IAClC,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;AAC7E,CAAC;AAED,uDAAuD;AACvD,KAAK,UAAU,OAAO,CAAC,GAAW,EAAE,MAAe;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC;QAC9B,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;YAChB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;YAC1D,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAChD,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,GAAG;gBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;YACnE,GAAG,CAAC,SAAS,CACX,GAAG,EACH,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,KAAK;YACX,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,KAAK;YACb,CAAC,EACD,CAAC,EACD,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,MAAM,CACd,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC;QACF,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/B,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,OAAY;IACvD,IAAI,CAAC;QACH,sGAAsG;QACtG,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,GAAG,wBAAwB,EAAE,CAAC;YACjE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACzD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,KAAK,CAAC,CAAC,CAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QACxE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAE1D,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;QACzD,CAAC;QACD,kDAAkD;QAClD,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IACjD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAKD,oBAAoB;AACpB,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAS;IAC5D,8CAA8C;IAC9C,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,sDAAsD;IACtD,wDAAwD;IAExD,4DAA4D;IAC5D,MAAM,IAAI,GAAG;QACX,OAAO,EAAE,IAAI,CAAC,IAAI;QAClB,OAAO,EAAE,IAAI,CAAC,IAAI;QAClB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;IAEF,wBAAwB;IACxB,wJAAwJ;IACxJ,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,eAAe,CACnD,GAAG,EACH,CAAC,EAAE,IAAI,EAAE,CAAC,EACV,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,UAAU,CAAC,GAAG,EAAE,CACzD,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3C,OAAO,MAAM,CAAC,GAAG,CAAC;AACpB,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,GAAW,EAAE,IAAS,EAAE,YAAoB,GAAG;IAClG,OAAO,CAAC,GAAG,CAAC,gCAAgC,EAAE,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEjH,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,IAAI,UAAU,GAAG,KAAK,CAAC;QAEvB,oCAAoC;QACpC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;gBACzF,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,6CAA6C;YAC7D,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,8CAA8C;QAC9C,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,EAAE;YAC/C,IAAI,UAAU;gBAAE,OAAO;YAEvB,2DAA2D;YAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;YAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YAE3C,wCAAwC;YACxC,oDAAoD;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC;YAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;YAE5E,yCAAyC;YACzC,MAAM,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;YACrC,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,CAAC;YAEtC,MAAM,IAAI,GAAG;gBACX,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,OAAO;gBAChB,KAAK,EAAE,UAAU;gBACjB,MAAM,EAAE,WAAW;aACpB,CAAC;YAEF,MAAM,CAAC,GAAG,CAAC,8CAA8C,EAAE,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAEvJ,uCAAuC;YACvC,wJAAwJ;YACxJ,gBAAgB,CAAC,eAAe,CAC9B,GAAG,EACH,CAAC,EAAE,IAAI,EAAE,CAAC,EACV,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,UAAU,CAAC,IAAI,EAAE,CAC7D;iBACE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBACf,IAAI,UAAU;oBAAE,OAAO;gBACvB,UAAU,GAAG,IAAI,CAAC;gBAClB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,gCAAgC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC1D,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,IAAI,UAAU;oBAAE,OAAO;gBACvB,UAAU,GAAG,IAAI,CAAC;gBAClB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;gBAC/D,+DAA+D;gBAC/D,OAAO,CAAC,GAAG,CAAC,CAAC;YACf,CAAC,CAAC,CAAC;QACP,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;YACX,IAAI,UAAU;gBAAE,OAAO;YACvB,UAAU,GAAG,IAAI,CAAC;YAClB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,yDAAyD,EAAE,KAAK,CAAC,CAAC;YAChF,+DAA+D;YAC/D,OAAO,CAAC,GAAG,CAAC,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { Image as RNImage, Platform } from 'react-native';\nimport * as ImageManipulator from \"expo-image-manipulator\";\nimport { truncateFields } from '../modules/api/KYCService';\nimport { logger } from './logger';\n\ntype Point = [number, number];\n\n/** OBB confidence below this = card not fully in frame; don't crop, give user feedback. */\nexport const OBB_CONFIDENCE_THRESHOLD = 0.85;\n\n/**\n * card_obb format from API:\n * - Legacy: [ [ [p1,p2,p3,p4], confidence ] ]\n * - New: [ { obb: [p1,p2,p3,p4], confidence, card_in_frame?, cropped_sides? } ]\n * Returns confidence in [0,1] or null if not present.\n */\nexport function getObbConfidence(cardObb: any): number | null {\n if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;\n const first = cardObb[0];\n\n // New format: first is an object with a confidence field\n if (first && typeof first === 'object' && 'confidence' in first) {\n const score = (first as any).confidence;\n return typeof score === 'number' ? score : null;\n }\n\n // Legacy format: [ [points], score ]\n if (!Array.isArray(first) || first.length < 2) return null;\n const score = first[1];\n return typeof score === 'number' ? score : null;\n}\n\n/**\n * Extracts the card_in_frame flag from card_obb when present.\n * Returns:\n * - true => card fully in frame\n * - false => card not fully in frame\n * - null => field not present (backward compatibility)\n */\n/**\n * Extracts the framing data from card_obb.\n * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.\n * Returns:\n * - true => card fully in frame (no sides cropped)\n * - false => card not fully in frame OR edges are cropped\n * - null => field not present (backward compatibility)\n */\nexport function getCardInFrame(cardObb: any): boolean | null {\n if (!cardObb) return null;\n\n // card_obb can be an array of objects or a single object\n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n if (first && typeof first === 'object' && 'card_in_frame' in first) {\n const isCardInFrame = first.card_in_frame === true;\n const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;\n\n // If the API explicitly says it's not in frame, OR if it lists cropped edges, reject it.\n if (!isCardInFrame || hasCroppedSides) {\n if (hasCroppedSides) {\n console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);\n }\n return false; \n }\n\n // If we made it here, card_in_frame is true AND cropped_sides is empty.\n return true; \n }\n\n // Backward compatibility check\n if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {\n const value = (cardObb as any).card_in_frame;\n return typeof value === 'boolean' ? value : null;\n }\n\n return null;\n}\n\n// Compute axis-aligned bounding box from oriented quadrilateral\nfunction computeAabb(points: Point[]) {\n const xs = points.map(p => p[0]);\n const ys = points.map(p => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const maxX = Math.max(...xs);\n const maxY = Math.max(...ys);\n return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n\n// Web-only crop using Canvas; returns dataURL (base64)\nasync function cropWeb(uri: string, points: Point[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n const { minX, minY, width, height } = computeAabb(points);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext('2d');\n if (!ctx) return reject(new Error('Canvas context not available'));\n ctx.drawImage(\n img,\n minX, // sx\n minY, // sy\n width, // sw\n height, // sh\n 0,\n 0,\n canvas.width,\n canvas.height\n );\n resolve(canvas.toDataURL('image/jpeg', 0.92));\n };\n img.onerror = (e) => reject(e);\n img.src = uri;\n });\n}\n\n// Fallback: return original for native (no dependency added); caller can still use bbox to draw overlay\nexport async function cropByObb(uri: string, cardObb: any): Promise<{ base64?: string; bbox?: { minX: number; minY: number; width: number; height: number } }> {\n try {\n // card_obb format: [ [ [p1,p2,p3,p4], score ] ] — if confidence too low, don't crop (send full image)\n const confidence = getObbConfidence(cardObb);\n if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {\n return {};\n }\n const first = Array.isArray(cardObb) ? cardObb[0] : null;\n const points = Array.isArray(first?.[0]) ? (first[0] as Point[]) : null;\n if (!points || points.length !== 4) return {};\n\n const { minX, minY, width, height } = computeAabb(points);\n\n if (Platform.OS === 'web') {\n const base64 = await cropWeb(uri, points);\n return { base64, bbox: { minX, minY, width, height } };\n }\n // Native: return bbox only; keep base64 undefined\n return { bbox: { minX, minY, width, height } };\n } catch (e) {\n return {};\n }\n}\n\n\n\n\n// exemple d'appel :\nexport async function cropImageWithBBox(uri: string, bbox: any) {\n // 1️⃣ Récupère la taille originale de l'image\n await RNImage.getSize(uri, (width, height) => {\n console.log(\"Image originale:\", width, height);\n });\n\n // // 2️⃣ Suppose que ton bbox vient d'une image affichée dans `displayedSize`\n // const scaleX = originalWidth / displayedSize.width;\n // const scaleY = originalHeight / displayedSize.height;\n\n // 3️⃣ Convertir le bbox à la taille réelle scale 0.10 = 10%\n const crop = {\n originX: bbox.minX,\n originY: bbox.minY,\n width: bbox.width,\n height: bbox.height,\n };\n\n // 4️⃣ Appliquer le crop\n // @ts-ignore - manipulateAsync is deprecated but still functional, new API (useImageManipulator) is a React hook and not suitable for utility functions\n const result = await ImageManipulator.manipulateAsync(\n uri,\n [{ crop }],\n { compress: 1, format: ImageManipulator.SaveFormat.PNG }\n );\n\n console.log(\"Image recadrée:\", result.uri);\n return result.uri;\n}\n\n// Fonction pour rogner avec une tolérance de 10% autour du bbox\nexport async function cropImageWithBBoxWithTolerance(uri: string, bbox: any, tolerance: number = 0.1): Promise<string> {\n console.log(\"cropImageWithBBoxWithTolerance\", JSON.stringify(truncateFields({ uri, bbox, tolerance }), null, 2));\n \n return new Promise<string>((resolve, reject) => {\n let isResolved = false;\n \n // Timeout de sécurité (15 secondes)\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n console.warn(\"Timeout lors du rognage avec tolérance, utilisation de l'image originale\");\n resolve(uri); // Retourner l'URI original en cas de timeout\n }\n }, 15000);\n\n // 1️⃣ Récupère la taille originale de l'image\n RNImage.getSize(uri, (imageWidth, imageHeight) => {\n if (isResolved) return;\n \n // 2️⃣ Calculer la tolérance en pixels (10% de chaque côté)\n const toleranceX = bbox.width * tolerance;\n const toleranceY = bbox.height * tolerance;\n\n // 3️⃣ Ajuster le bbox avec la tolérance\n // Calculer les nouvelles coordonnées avec tolérance\n const newMinX = Math.max(0, bbox.minX - toleranceX);\n const newMinY = Math.max(0, bbox.minY - toleranceY);\n const newMaxX = Math.min(imageWidth, bbox.minX + bbox.width + toleranceX);\n const newMaxY = Math.min(imageHeight, bbox.minY + bbox.height + toleranceY);\n\n // Calculer la largeur et hauteur finales\n const finalWidth = newMaxX - newMinX;\n const finalHeight = newMaxY - newMinY;\n\n const crop = {\n originX: newMinX,\n originY: newMinY,\n width: finalWidth,\n height: finalHeight,\n };\n\n logger.log(\"cropImageWithBBoxWithTolerance - crop params\", JSON.stringify(truncateFields({ uri, crop, tolerance, imageWidth, imageHeight }), null, 2));\n\n // 4️⃣ Appliquer le crop avec tolérance\n // @ts-ignore - manipulateAsync is deprecated but still functional, new API (useImageManipulator) is a React hook and not suitable for utility functions\n ImageManipulator.manipulateAsync(\n uri,\n [{ crop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n )\n .then((result) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n console.log(\"Image recadrée avec tolérance:\", result.uri);\n resolve(result.uri);\n })\n .catch((error) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Erreur lors du rognage avec tolérance:\", error);\n // En cas d'erreur, retourner l'URI original au lieu de rejeter\n resolve(uri);\n });\n }, (error) => {\n if (isResolved) return;\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"Erreur lors de la récupération de la taille de l'image:\", error);\n // En cas d'erreur, retourner l'URI original au lieu de rejeter\n resolve(uri);\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cropByObb.js","sourceRoot":"","sources":["../../../src/utils/cropByObb.ts"],"names":[],"mappings":"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","sourcesContent":["import { Image as RNImage, Platform } from 'react-native';\nimport * as ImageManipulator from \"expo-image-manipulator\";\n\n\ntype Point = [number, number];\n\n/** OBB confidence below this = card not fully in frame; don't crop, give user feedback. */\nexport const OBB_CONFIDENCE_THRESHOLD = 0.85;\n\n/**\n * card_obb format from API:\n * - Legacy: [ [ [p1,p2,p3,p4], confidence ] ]\n * - New: [ { obb: [p1,p2,p3,p4], confidence, card_in_frame?, cropped_sides? } ]\n * Returns confidence in [0,1] or null if not present.\n */\nexport function getObbConfidence(cardObb: any): number | null {\n if (!cardObb || !Array.isArray(cardObb) || cardObb.length === 0) return null;\n const first = cardObb[0];\n\n // New format: first is an object with a confidence field\n if (first && typeof first === 'object' && 'confidence' in first) {\n const score = (first as any).confidence;\n return typeof score === 'number' ? score : null;\n }\n\n // Legacy format: [ [points], score ]\n if (!Array.isArray(first) || first.length < 2) return null;\n const score = first[1];\n return typeof score === 'number' ? score : null;\n}\n\n/**\n * Extracts the framing data from card_obb.\n * STRICT CHECK: Returns true ONLY if card_in_frame is true AND cropped_sides is empty.\n */\nexport function getCardInFrame(cardObb: any): boolean | null {\n if (!cardObb) return null;\n \n // card_obb can be an array of objects or a single object\n const first = Array.isArray(cardObb) ? cardObb[0] : cardObb;\n\n if (first && typeof first === 'object' && 'card_in_frame' in first) {\n const isCardInFrame = first.card_in_frame === true;\n const hasCroppedSides = Array.isArray(first.cropped_sides) && first.cropped_sides.length > 0;\n\n // If the API explicitly says it's not in frame, OR if it lists cropped edges, reject it.\n if (!isCardInFrame || hasCroppedSides) {\n if (hasCroppedSides) {\n console.warn(`Card is cropped on sides: ${first.cropped_sides.join(', ')}`);\n }\n return false;\n }\n\n // If we made it here, card_in_frame is true AND cropped_sides is empty.\n return true;\n }\n\n // Backward compatibility check\n if (cardObb && typeof cardObb === 'object' && 'card_in_frame' in cardObb) {\n const value = (cardObb as any).card_in_frame;\n return typeof value === 'boolean' ? value : null;\n }\n\n return null;\n}\n\n// Compute axis-aligned bounding box from oriented quadrilateral\nfunction computeAabb(points: Point[]) {\n const xs = points.map(p => p[0]);\n const ys = points.map(p => p[1]);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n const maxX = Math.max(...xs);\n const maxY = Math.max(...ys);\n return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };\n}\n\n// Web-only crop using Canvas; returns dataURL (base64)\nasync function cropWeb(uri: string, points: Point[]): Promise<string> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n const { minX, minY, width, height } = computeAabb(points);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width));\n canvas.height = Math.max(1, Math.round(height));\n const ctx = canvas.getContext('2d');\n if (!ctx) return reject(new Error('Canvas context not available'));\n ctx.drawImage(\n img,\n minX, minY, width, height, // Source \n 0, 0, canvas.width, canvas.height // Destination\n );\n resolve(canvas.toDataURL('image/jpeg', 0.92));\n };\n img.onerror = (e) => reject(e);\n img.src = uri;\n });\n}\n\n// Fallback: return original for native\nexport async function cropByObb(uri: string, cardObb: any): Promise<{ base64?: string; bbox?: { minX: number; minY: number; width: number; height: number } }> {\n try {\n const confidence = getObbConfidence(cardObb);\n if (confidence !== null && confidence < OBB_CONFIDENCE_THRESHOLD) {\n return {};\n }\n const first = Array.isArray(cardObb) ? cardObb[0] : null;\n const points = Array.isArray(first?.[0]) ? (first[0] as Point[]) : null;\n if (!points || points.length !== 4) return {};\n\n const { minX, minY, width, height } = computeAabb(points);\n\n if (Platform.OS === 'web') {\n const base64 = await cropWeb(uri, points);\n return { base64, bbox: { minX, minY, width, height } };\n }\n \n return { bbox: { minX, minY, width, height } };\n } catch (e) {\n return {};\n }\n}\n\n\nexport async function cropImageWithBBoxWithTolerance(uri: string, bbox: any, tolerance: number = 0.02): Promise<string> {\n return new Promise<string>((resolve) => {\n let isResolved = false;\n\n // Safety Timeout (10 seconds)\n const timeout = setTimeout(() => {\n if (!isResolved) {\n isResolved = true;\n console.warn(\"Crop timeout, returning original.\");\n resolve(uri);\n }\n }, 10000);\n\n try {\n RNImage.getSize(uri, async (actualWidth, actualHeight) => {\n if (isResolved) return;\n\n // 1. Detect if bbox is percentage (e.g., 0.15) or absolute pixels (e.g., 800)\n // Since we pass UI percentages, this will be true.\n const isPercentageBased = bbox.width <= 1.5 && bbox.height <= 1.5;\n\n let pixelMinX, pixelMinY, pixelWidth, pixelHeight;\n\n if (isPercentageBased) {\n pixelMinX = bbox.minX * actualWidth;\n pixelMinY = bbox.minY * actualHeight;\n pixelWidth = bbox.width * actualWidth;\n pixelHeight = bbox.height * actualHeight;\n } else {\n // Fallback if backend pixels somehow bypass the check\n pixelMinX = bbox.minX;\n pixelMinY = bbox.minY;\n pixelWidth = bbox.width;\n pixelHeight = bbox.height;\n }\n\n // 2. Add Tolerance (Expands the box slightly to avoid cutting off physical card edges)\n const toleranceX = pixelWidth * tolerance;\n const toleranceY = pixelHeight * tolerance;\n\n // Clamp values to ensure we don't try to crop outside the image bounds\n const newMinX = Math.max(0, pixelMinX - toleranceX);\n const newMinY = Math.max(0, pixelMinY - toleranceY);\n const newMaxX = Math.min(actualWidth, pixelMinX + pixelWidth + toleranceX);\n const newMaxY = Math.min(actualHeight, pixelMinY + pixelHeight + toleranceY);\n\n const finalCrop = {\n originX: Math.floor(newMinX),\n originY: Math.floor(newMinY),\n width: Math.floor(newMaxX - newMinX),\n height: Math.floor(newMaxY - newMinY),\n };\n\n // 3. Perform Final Crop\n try {\n // @ts-ignore - Required for utility functions outside React components\n const result = await ImageManipulator.manipulateAsync(\n uri,\n [{ crop: finalCrop }],\n { compress: 0.95, format: ImageManipulator.SaveFormat.JPEG }\n );\n \n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(result.uri);\n }\n } catch (err) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n console.error(\"ImageManipulator failed, returning raw image\", err);\n resolve(uri);\n }\n }\n }, () => {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(uri);\n }\n });\n } catch (e) {\n if (!isResolved) {\n isResolved = true;\n clearTimeout(timeout);\n resolve(uri);\n }\n }\n });\n}"]}
|
package/package.json
CHANGED
|
@@ -1,34 +1,201 @@
|
|
|
1
|
+
// import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
// import { View, StyleSheet, Text, AppState } from 'react-native';
|
|
3
|
+
// import { Camera, useCameraDevice, useCameraFormat } from 'react-native-vision-camera';
|
|
4
|
+
// import VisionCameraModule from '../modules/camera/VisionCameraModule';
|
|
5
|
+
// import { useI18n } from '../hooks/useI18n';
|
|
6
|
+
// import { EnhancedCameraViewProps } from './OverLay/type';
|
|
7
|
+
// import { Button } from './ui/Button';
|
|
8
|
+
|
|
9
|
+
// export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
10
|
+
// showCamera,
|
|
11
|
+
// cameraType: initialCameraType = 'front',
|
|
12
|
+
// style,
|
|
13
|
+
// onError,
|
|
14
|
+
// onSilentCapture,
|
|
15
|
+
// silentCaptureResult,
|
|
16
|
+
// isProcessing = false,
|
|
17
|
+
// overlayComponent,
|
|
18
|
+
// }) => {
|
|
19
|
+
// const { t } = useI18n();
|
|
20
|
+
// const camera = useRef<Camera>(null);
|
|
21
|
+
|
|
22
|
+
// const isCapturingRef = useRef(false);
|
|
23
|
+
// const isProcessingRef = useRef(isProcessing);
|
|
24
|
+
|
|
25
|
+
// const [cameraType] = useState<'front' | 'back'>(initialCameraType);
|
|
26
|
+
// const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
|
27
|
+
// const [isInitialized, setIsInitialized] = useState(false);
|
|
28
|
+
// const [refreshCamera, setRefreshCamera] = useState(false);
|
|
29
|
+
// const [layout, setLayout] = useState({ width: 0, height: 0 });
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// const device = useCameraDevice(cameraType, {
|
|
33
|
+
// physicalDevices: [
|
|
34
|
+
// 'wide-angle-camera' // Explicitly request standard 1x lens
|
|
35
|
+
// ]
|
|
36
|
+
// });
|
|
37
|
+
|
|
38
|
+
// const targetRatio = layout.width > 0 ? layout.height / layout.width : 16 / 9;
|
|
39
|
+
|
|
40
|
+
// const format = useCameraFormat(device, [
|
|
41
|
+
// { photoAspectRatio: targetRatio },
|
|
42
|
+
// { photoResolution: 'max' }
|
|
43
|
+
// ]);
|
|
44
|
+
|
|
45
|
+
// useEffect(() => {
|
|
46
|
+
// isProcessingRef.current = isProcessing;
|
|
47
|
+
// }, [isProcessing]);
|
|
48
|
+
|
|
49
|
+
// const checkPermissions = async () => {
|
|
50
|
+
// try {
|
|
51
|
+
// const hasAllPermissions = await VisionCameraModule.hasAllPermissions();
|
|
52
|
+
// if (!hasAllPermissions) {
|
|
53
|
+
// const granted = await VisionCameraModule.requestAllPermissions();
|
|
54
|
+
// if (!granted) {
|
|
55
|
+
// setHasPermission(false);
|
|
56
|
+
// onError?.({ message: t('camera.permissionRequired') });
|
|
57
|
+
// return;
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
// setHasPermission(true);
|
|
61
|
+
// } catch (error) {
|
|
62
|
+
// setHasPermission(false);
|
|
63
|
+
// onError?.({ message: t('camera.errorOccurred') });
|
|
64
|
+
// }
|
|
65
|
+
// };
|
|
66
|
+
|
|
67
|
+
// useEffect(() => {
|
|
68
|
+
// if (showCamera) checkPermissions();
|
|
69
|
+
// }, [showCamera, refreshCamera]);
|
|
70
|
+
|
|
71
|
+
// useEffect(() => {
|
|
72
|
+
// const subscription = AppState.addEventListener('change', nextAppState => {
|
|
73
|
+
// if (nextAppState === 'active' && showCamera && hasPermission === false) {
|
|
74
|
+
// checkPermissions();
|
|
75
|
+
// }
|
|
76
|
+
// });
|
|
77
|
+
// return () => subscription.remove();
|
|
78
|
+
// }, [showCamera, hasPermission]);
|
|
79
|
+
|
|
80
|
+
// const onInitialized = useCallback(() => setIsInitialized(true), []);
|
|
81
|
+
// const onCameraError = useCallback((error: any) => {
|
|
82
|
+
// onError?.({ message: error.message || t('camera.errorOccurred') });
|
|
83
|
+
// }, [onError, t]);
|
|
84
|
+
|
|
85
|
+
// const captureSilentPhoto = useCallback(async () => {
|
|
86
|
+
// if (!camera.current || !isInitialized || isProcessingRef.current || isCapturingRef.current) return;
|
|
87
|
+
// if (silentCaptureResult?.isAnalyzing) return;
|
|
88
|
+
|
|
89
|
+
// try {
|
|
90
|
+
// isCapturingRef.current = true;
|
|
91
|
+
// const photo = await camera.current.takePhoto({
|
|
92
|
+
// enableShutterSound: false,
|
|
93
|
+
// flash: 'off',
|
|
94
|
+
// });
|
|
95
|
+
// const result = await VisionCameraModule.processPhotoResult(photo);
|
|
96
|
+
|
|
97
|
+
// onSilentCapture?.({
|
|
98
|
+
// ...result,
|
|
99
|
+
// path: result.path || photo.path,
|
|
100
|
+
// });
|
|
101
|
+
// } catch (error) {
|
|
102
|
+
// // Silent background fail
|
|
103
|
+
// } finally {
|
|
104
|
+
// isCapturingRef.current = false;
|
|
105
|
+
// }
|
|
106
|
+
// }, [isInitialized, onSilentCapture, silentCaptureResult]);
|
|
107
|
+
|
|
108
|
+
// useEffect(() => {
|
|
109
|
+
// if (!showCamera || !isInitialized || isProcessing) return;
|
|
110
|
+
// let isActive = true;
|
|
111
|
+
// let intervalId: ReturnType<typeof setInterval>;
|
|
112
|
+
|
|
113
|
+
// const warmupTimer = setTimeout(() => {
|
|
114
|
+
// if (!isActive) return;
|
|
115
|
+
// intervalId = setInterval(() => {
|
|
116
|
+
// captureSilentPhoto();
|
|
117
|
+
// }, 1500);
|
|
118
|
+
// }, 1000);
|
|
119
|
+
|
|
120
|
+
// return () => {
|
|
121
|
+
// isActive = false;
|
|
122
|
+
// clearTimeout(warmupTimer);
|
|
123
|
+
// if (intervalId) clearInterval(intervalId);
|
|
124
|
+
// };
|
|
125
|
+
// }, [showCamera, isInitialized, isProcessing, captureSilentPhoto]);
|
|
126
|
+
|
|
127
|
+
// if (hasPermission === null) return <View style={[styles.container, style]} />;
|
|
128
|
+
|
|
129
|
+
// if (hasPermission === false) {
|
|
130
|
+
// return (
|
|
131
|
+
// <View style={[styles.container, style, { justifyContent: 'center', alignItems: 'center' }]}>
|
|
132
|
+
// <Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>
|
|
133
|
+
// <Button title="Refresh Camera" onPress={() => setRefreshCamera(prev => !prev)} variant="primary" />
|
|
134
|
+
// </View>
|
|
135
|
+
// );
|
|
136
|
+
// }
|
|
137
|
+
|
|
138
|
+
// if (!device || !showCamera) return <View style={[styles.container, style]} />;
|
|
139
|
+
|
|
140
|
+
// return (
|
|
141
|
+
// <View
|
|
142
|
+
// style={[styles.container, style]}
|
|
143
|
+
// onLayout={(e) => setLayout({ width: e.nativeEvent.layout.width, height: e.nativeEvent.layout.height })}
|
|
144
|
+
// >
|
|
145
|
+
// <Camera
|
|
146
|
+
// ref={camera}
|
|
147
|
+
// style={StyleSheet.absoluteFill}
|
|
148
|
+
// device={device}
|
|
149
|
+
// format={format}
|
|
150
|
+
// resizeMode="cover"
|
|
151
|
+
// isActive={showCamera && !isProcessing}
|
|
152
|
+
// photo={true}
|
|
153
|
+
// video={false}
|
|
154
|
+
// audio={false}
|
|
155
|
+
// onInitialized={onInitialized}
|
|
156
|
+
// onError={onCameraError}
|
|
157
|
+
// />
|
|
158
|
+
// {overlayComponent}
|
|
159
|
+
// </View>
|
|
160
|
+
// );
|
|
161
|
+
// };
|
|
162
|
+
|
|
163
|
+
// const styles = StyleSheet.create({
|
|
164
|
+
// container: { flex: 1, backgroundColor: 'black' },
|
|
165
|
+
// permissionMessage: { color: 'white', textAlign: 'center', margin: 20, fontSize: 16 },
|
|
166
|
+
// });
|
|
167
|
+
|
|
1
168
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
169
|
import { View, StyleSheet, Text, AppState } from 'react-native';
|
|
3
|
-
import { Camera, useCameraDevice } from 'react-native-vision-camera';
|
|
170
|
+
import { Camera, useCameraDevice } from 'react-native-vision-camera';
|
|
4
171
|
import VisionCameraModule from '../modules/camera/VisionCameraModule';
|
|
5
172
|
import { useI18n } from '../hooks/useI18n';
|
|
6
173
|
import { EnhancedCameraViewProps } from './OverLay/type';
|
|
7
174
|
import { Button } from './ui/Button';
|
|
8
175
|
|
|
9
|
-
export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
176
|
+
export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
10
177
|
showCamera,
|
|
11
178
|
cameraType: initialCameraType = 'front',
|
|
12
179
|
style,
|
|
13
180
|
onError,
|
|
14
|
-
onSilentCapture,
|
|
181
|
+
onSilentCapture,
|
|
15
182
|
silentCaptureResult,
|
|
16
|
-
isProcessing = false,
|
|
183
|
+
isProcessing = false,
|
|
17
184
|
overlayComponent,
|
|
18
185
|
}) => {
|
|
19
186
|
const { t } = useI18n();
|
|
20
187
|
const camera = useRef<Camera>(null);
|
|
21
|
-
|
|
188
|
+
|
|
22
189
|
const isCapturingRef = useRef(false);
|
|
23
190
|
const isProcessingRef = useRef(isProcessing);
|
|
24
191
|
|
|
25
192
|
const [cameraType] = useState<'front' | 'back'>(initialCameraType);
|
|
26
|
-
|
|
27
|
-
// 🚨 BUG FIX: Initialize to null to prevent the "Flicker" on retake
|
|
28
193
|
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
|
29
194
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
30
195
|
const [refreshCamera, setRefreshCamera] = useState(false);
|
|
31
196
|
|
|
197
|
+
// 🚨 THE FIX: Removed the strict physical device filtering and custom format.
|
|
198
|
+
// This allows Android to select its own supported, stable hardware stream, preventing crashes.
|
|
32
199
|
const device = useCameraDevice(cameraType);
|
|
33
200
|
|
|
34
201
|
useEffect(() => {
|
|
@@ -76,27 +243,25 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
76
243
|
if (silentCaptureResult?.isAnalyzing) return;
|
|
77
244
|
|
|
78
245
|
try {
|
|
79
|
-
isCapturingRef.current = true;
|
|
246
|
+
isCapturingRef.current = true;
|
|
80
247
|
const photo = await camera.current.takePhoto({
|
|
81
|
-
enableShutterSound: false,
|
|
82
|
-
flash: 'off',
|
|
248
|
+
enableShutterSound: false,
|
|
249
|
+
flash: 'off',
|
|
83
250
|
});
|
|
84
|
-
|
|
85
|
-
const result = await VisionCameraModule.processPhotoResult(photo);
|
|
86
251
|
|
|
252
|
+
const result = await VisionCameraModule.processPhotoResult(photo);
|
|
253
|
+
|
|
87
254
|
onSilentCapture?.({
|
|
88
|
-
|
|
89
|
-
|
|
255
|
+
...result,
|
|
256
|
+
path: result.path || photo.path,
|
|
90
257
|
});
|
|
91
|
-
|
|
92
258
|
} catch (error) {
|
|
93
259
|
// Silent background fail
|
|
94
260
|
} finally {
|
|
95
|
-
isCapturingRef.current = false;
|
|
261
|
+
isCapturingRef.current = false;
|
|
96
262
|
}
|
|
97
263
|
}, [isInitialized, onSilentCapture, silentCaptureResult]);
|
|
98
264
|
|
|
99
|
-
// 🚨 BUG FIX: The Warm-up Timer (Fixes Blurry Images)
|
|
100
265
|
useEffect(() => {
|
|
101
266
|
if (!showCamera || !isInitialized || isProcessing) return;
|
|
102
267
|
|
|
@@ -107,9 +272,9 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
107
272
|
if (!isActive) return;
|
|
108
273
|
intervalId = setInterval(() => {
|
|
109
274
|
captureSilentPhoto();
|
|
110
|
-
}, 1500);
|
|
111
|
-
}, 1000);
|
|
112
|
-
|
|
275
|
+
}, 1500);
|
|
276
|
+
}, 1000);
|
|
277
|
+
|
|
113
278
|
return () => {
|
|
114
279
|
isActive = false;
|
|
115
280
|
clearTimeout(warmupTimer);
|
|
@@ -117,22 +282,13 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
117
282
|
};
|
|
118
283
|
}, [showCamera, isInitialized, isProcessing, captureSilentPhoto]);
|
|
119
284
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// 🚨 BUG FIX: Show nothing while checking permissions (Stops flicker)
|
|
123
|
-
if (hasPermission === null) {
|
|
124
|
-
return <View style={[styles.container, style]} />;
|
|
125
|
-
}
|
|
285
|
+
if (hasPermission === null) return <View style={[styles.container, style]} />;
|
|
126
286
|
|
|
127
287
|
if (hasPermission === false) {
|
|
128
288
|
return (
|
|
129
289
|
<View style={[styles.container, style, { justifyContent: 'center', alignItems: 'center' }]}>
|
|
130
290
|
<Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>
|
|
131
|
-
<Button
|
|
132
|
-
title="Refresh Camera"
|
|
133
|
-
onPress={() => setRefreshCamera(prev => !prev)}
|
|
134
|
-
variant="primary"
|
|
135
|
-
/>
|
|
291
|
+
<Button title="Refresh Camera" onPress={() => setRefreshCamera(prev => !prev)} variant="primary" />
|
|
136
292
|
</View>
|
|
137
293
|
);
|
|
138
294
|
}
|
|
@@ -145,10 +301,11 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
145
301
|
ref={camera}
|
|
146
302
|
style={StyleSheet.absoluteFill}
|
|
147
303
|
device={device}
|
|
304
|
+
resizeMode="cover"
|
|
148
305
|
isActive={showCamera && !isProcessing}
|
|
149
306
|
photo={true}
|
|
150
|
-
video={false}
|
|
151
|
-
audio={false}
|
|
307
|
+
video={false}
|
|
308
|
+
audio={false}
|
|
152
309
|
onInitialized={onInitialized}
|
|
153
310
|
onError={onCameraError}
|
|
154
311
|
/>
|
|
@@ -87,9 +87,12 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
87
87
|
const cameraConfig = useMemo(() => {
|
|
88
88
|
const instructions = selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions as Record<string, LocalizedText>);
|
|
89
89
|
return {
|
|
90
|
-
cameraType: Platform.OS === 'web' ? cameraType : 'back',
|
|
90
|
+
cameraType: Platform.OS === 'web' ? cameraType : 'back',
|
|
91
91
|
flashMode: 'auto' as const,
|
|
92
|
-
overlay: {
|
|
92
|
+
overlay: {
|
|
93
|
+
guideText: instructions,
|
|
94
|
+
bbox: { xMin: 5, yMin: 15, xMax: 95, yMax: 85, borderColor: '#2DBD60', borderWidth: 3, cornerRadius: 8 }
|
|
95
|
+
}
|
|
93
96
|
};
|
|
94
97
|
}, [selectedDocumentType, locale, component.instructions, cameraType]);
|
|
95
98
|
|
|
@@ -153,21 +156,36 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
153
156
|
if (isProcessingCapture) return;
|
|
154
157
|
setIsProcessingCapture(true);
|
|
155
158
|
setProcessingImagePath(capturePath);
|
|
159
|
+
|
|
156
160
|
try {
|
|
157
161
|
let imagePathForUpload = capturePath;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
|
|
163
|
+
// 🚨 THE FIX: Ignore the backend's broken absolute pixels.
|
|
164
|
+
// Calculate the crop using the Green UI Frame percentages (0.0 to 1.0)
|
|
165
|
+
const overlayBbox = cameraConfig.overlay.bbox;
|
|
166
|
+
const uiCropBbox = {
|
|
167
|
+
minX: overlayBbox.xMin / 100,
|
|
168
|
+
minY: overlayBbox.yMin / 100,
|
|
169
|
+
width: (overlayBbox.xMax - overlayBbox.xMin) / 100,
|
|
170
|
+
height: (overlayBbox.yMax - overlayBbox.yMin) / 100,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Apply the crop using the UI percentages and a tight 2% tolerance
|
|
175
|
+
imagePathForUpload = await cropImageWithBBoxWithTolerance(capturePath, uiCropBbox, 0.02);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.warn("Crop failed, falling back to original image", e);
|
|
178
|
+
imagePathForUpload = capturePath; // Fallback to raw image if crop fails
|
|
164
179
|
}
|
|
180
|
+
|
|
165
181
|
const base64 = await pathToBase64(imagePathForUpload);
|
|
166
182
|
const newImages = {
|
|
167
183
|
...capturedImages,
|
|
168
184
|
[currentSide]: { dir: imagePathForUpload, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
|
|
169
185
|
};
|
|
186
|
+
|
|
170
187
|
setCapturedImages(newImages);
|
|
188
|
+
|
|
171
189
|
if (verified.country && verified.documentType) {
|
|
172
190
|
onValueChange({
|
|
173
191
|
...newImages,
|
|
@@ -175,6 +193,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
175
193
|
documentType: GovernmentDocumentTypeBackend[verified.documentType as keyof typeof GovernmentDocumentTypeBackend] || '',
|
|
176
194
|
});
|
|
177
195
|
}
|
|
196
|
+
|
|
178
197
|
setTimeout(() => {
|
|
179
198
|
setShowCamera(false);
|
|
180
199
|
actions.showCustomStepper(true);
|
|
@@ -182,6 +201,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
182
201
|
setIsProcessingCapture(false);
|
|
183
202
|
setProcessingImagePath(null);
|
|
184
203
|
}, 600);
|
|
204
|
+
|
|
185
205
|
} catch (e: any) {
|
|
186
206
|
console.error("Backend Error:", e);
|
|
187
207
|
const friendlyError = state.currentLanguage === 'en' ? 'Unable to process document. Please try again.' : 'Impossible de traiter le document. Veuillez réessayer.';
|
|
@@ -242,15 +262,29 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
242
262
|
if (bbox) setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
|
|
243
263
|
await autoCapture(result.path, verifiedResult);
|
|
244
264
|
} catch (error: any) {
|
|
245
|
-
|
|
265
|
+
// 1. Keep the technical log for your debugging console
|
|
266
|
+
console.error("Backend Verification Error:", error);
|
|
246
267
|
|
|
247
|
-
|
|
268
|
+
// 2. Define a map of technical keywords to user-friendly messages
|
|
269
|
+
const rawMessage = (error?.message || '').toLowerCase();
|
|
248
270
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
271
|
+
let userFriendlyMessage ='Verification failed. Please try again.';
|
|
272
|
+
|
|
273
|
+
if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
|
|
274
|
+
userFriendlyMessage = 'Document unreadable. Please hold the camera steady in good light.';
|
|
275
|
+
} else if (rawMessage.includes('country mismatch') || rawMessage.includes('pays sélectionné')) {
|
|
276
|
+
userFriendlyMessage = 'The document does not match your selected country.';
|
|
277
|
+
} else if (rawMessage.includes('too far') || rawMessage.includes('card_not_fully_in_frame')) {
|
|
278
|
+
userFriendlyMessage = 'Move closer to the document and ensure all corners are visible.';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 3. Set ONLY the friendly message to the UI state
|
|
282
|
+
setSilentCaptureResult(prev => ({
|
|
283
|
+
...prev,
|
|
284
|
+
isAnalyzing: false,
|
|
285
|
+
success: false,
|
|
286
|
+
error: userFriendlyMessage
|
|
287
|
+
}));
|
|
254
288
|
}
|
|
255
289
|
}
|
|
256
290
|
};
|