UNPKG

react-native-expo-cropper

Version:
701 lines (689 loc) 32.2 kB
"use strict"; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = CustomCamera; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _reactNativeSafeAreaContext = require("react-native-safe-area-context"); var _expoCamera = require("expo-camera"); var _vectorIcons = require("@expo/vector-icons"); 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 _t4 in e) "default" !== _t4 && {}.hasOwnProperty.call(e, _t4) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t4)) && (i.get || i.set) ? o(f, _t4, i) : f[_t4] = e[_t4]); return f; })(e, t); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); } function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); } function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } // Max width for camera preview on large screens (tablets) so it doesn't stretch full width var CAMERA_PREVIEW_MAX_WIDTH = 500; function CustomCamera(_ref) { var onPhotoCaptured = _ref.onPhotoCaptured, onCancel = _ref.onCancel, _ref$instructionText = _ref.instructionText, instructionText = _ref$instructionText === void 0 ? 'Place the tray inside the green box' : _ref$instructionText; var _useWindowDimensions = (0, _reactNative.useWindowDimensions)(), windowWidth = _useWindowDimensions.width; var cameraPreviewWidth = Math.min(windowWidth, CAMERA_PREVIEW_MAX_WIDTH); var _useState = (0, _react.useState)(false), _useState2 = _slicedToArray(_useState, 2), isReady = _useState2[0], setIsReady = _useState2[1]; var _useState3 = (0, _react.useState)(false), _useState4 = _slicedToArray(_useState3, 2), loadingBeforeCapture = _useState4[0], setLoadingBeforeCapture = _useState4[1]; var _useState5 = (0, _react.useState)(null), _useState6 = _slicedToArray(_useState5, 2), hasPermission = _useState6[0], setHasPermission = _useState6[1]; var _useState7 = (0, _react.useState)(undefined), _useState8 = _slicedToArray(_useState7, 2), pictureSize = _useState8[0], setPictureSize = _useState8[1]; var cameraRef = (0, _react.useRef)(null); var cameraWrapperRef = (0, _react.useRef)(null); var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)(); var _useState9 = (0, _react.useState)({ width: 0, height: 0, x: 0, y: 0 }), _useState0 = _slicedToArray(_useState9, 2), cameraWrapperLayout = _useState0[0], setCameraWrapperLayout = _useState0[1]; var _useState1 = (0, _react.useState)(null), _useState10 = _slicedToArray(_useState1, 2), greenFrame = _useState10[0], setGreenFrame = _useState10[1]; var preferredNativeSizes = ['3072x4080', '3024x4032', '3000x4000', '2448x3264']; var parseSizeString = function parseSizeString(size) { var match = String(size || '').match(/(\d+)\s*[xX]\s*(\d+)/); if (!match) return null; var width = Number(match[1]); var height = Number(match[2]); var longEdge = Math.max(width, height); var shortEdge = Math.min(width, height); var ratio = longEdge / shortEdge; return { label: size, width: width, height: height, area: width * height, normalized: "".concat(shortEdge, "x").concat(longEdge), isFourByThree: Math.abs(ratio - 4 / 3) < 0.04 }; }; var chooseBestPictureSize = function chooseBestPictureSize() { var sizes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; if (!Array.isArray(sizes) || sizes.length === 0) return undefined; var parsedSizes = sizes.map(parseSizeString).filter(Boolean); // 1) Force exact target sizes first (normalized to ignore orientation order) var _loop = function _loop() { var preferred = _preferredNativeSizes[_i]; var preferredParsed = parseSizeString(preferred); if (!preferredParsed) return 0; // continue var exact = parsedSizes.find(function (item) { return item.normalized === preferredParsed.normalized; }); if (exact) return { v: exact.label }; }, _ret; for (var _i = 0, _preferredNativeSizes = preferredNativeSizes; _i < _preferredNativeSizes.length; _i++) { _ret = _loop(); if (_ret === 0) continue; if (_ret) return _ret.v; } // 2) Prefer high-resolution 4:3 sizes (phone still-photo native aspect) // Guard against tiny legacy 4:3 modes (e.g. 640x480). var MIN_HIGH_RES_AREA = 2000000; // ~2MP floor var highResFourByThree = parsedSizes.filter(function (item) { return item.isFourByThree && item.area >= MIN_HIGH_RES_AREA; }).sort(function (a, b) { return b.area - a.area; }); if (highResFourByThree.length > 0) { return highResFourByThree[0].label; } // 3) Fallback to highest area available. parsedSizes.sort(function (a, b) { return b.area - a.area; }); if (parsedSizes.length > 0) { return parsedSizes[0].label; } // iOS may return quality presets like "High", "Medium", "Low". var highPreset = sizes.find(function (size) { return String(size).toLowerCase() === 'high'; }); return highPreset || sizes[0]; }; (0, _react.useEffect)(function () { _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() { var _yield$Camera$request, status; return _regenerator().w(function (_context) { while (1) switch (_context.n) { case 0: _context.n = 1; return _expoCamera.Camera.requestCameraPermissionsAsync(); case 1: _yield$Camera$request = _context.v; status = _yield$Camera$request.status; setHasPermission(status === 'granted'); case 2: return _context.a(2); } }, _callee); }))(); }, []); var requestPermissionAgain = /*#__PURE__*/function () { var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() { var _yield$Camera$request2, status, _t; return _regenerator().w(function (_context2) { while (1) switch (_context2.p = _context2.n) { case 0: _context2.p = 0; _context2.n = 1; return _expoCamera.Camera.requestCameraPermissionsAsync(); case 1: _yield$Camera$request2 = _context2.v; status = _yield$Camera$request2.status; setHasPermission(status === 'granted'); _context2.n = 3; break; case 2: _context2.p = 2; _t = _context2.v; setHasPermission(false); case 3: return _context2.a(2); } }, _callee2, null, [[0, 2]]); })); return function requestPermissionAgain() { return _ref3.apply(this, arguments); }; }(); // Helper function to wait for multiple render cycles (works on iOS) var waitForRender = function waitForRender() { var cycles = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 5; return new Promise(function (resolve) { var count = 0; var _tick = function tick() { count++; if (count >= cycles) { resolve(); } else { setImmediate(_tick); } }; setImmediate(_tick); }); }; // ✅ CRITICAL FIX: Calculate green frame coordinates relative to camera preview // The green frame should be calculated on the wrapper (as it's visually drawn there) // But we store it with wrapper dimensions so ImageCropper can map it correctly // // NOTE: Camera capture aspect ratio (typically 4:3) may differ from wrapper aspect ratio (9:16) // This is handled in ImageCropper by using "cover" mode to match preview content var calculateGreenFrameCoordinates = function calculateGreenFrameCoordinates() { var wrapperWidth = cameraWrapperLayout.width; var wrapperHeight = cameraWrapperLayout.height; if (wrapperWidth === 0 || wrapperHeight === 0) { console.warn("Camera wrapper layout not ready, cannot calculate green frame"); return null; } // ✅ Calculate green frame as percentage of WRAPPER var frameWidth = wrapperWidth * 0.85; // 85% of wrapper width var frameHeight = wrapperHeight * 0.70; // 70% of wrapper height var frameX = (wrapperWidth - frameWidth) / 2; // Centered horizontally var frameY = (wrapperHeight - frameHeight) / 2; // Centered vertically var frameCoords = { x: frameX, y: frameY, width: frameWidth, height: frameHeight, wrapperWidth: wrapperWidth, wrapperHeight: wrapperHeight, // ✅ Store percentages for easier mapping later percentX: frameX / wrapperWidth * 100, percentY: frameY / wrapperHeight * 100, percentWidth: 85, // 85% of wrapper width percentHeight: 70 // 70% of wrapper height }; console.log("✅ Green frame coordinates calculated:", frameCoords); return frameCoords; }; // 🔁 Keep green frame state in sync with wrapper layout so we can both render it // and send the exact same coordinates along with the captured photo. (0, _react.useEffect)(function () { var coords = calculateGreenFrameCoordinates(); if (coords) { setGreenFrame(coords); } }, [cameraWrapperLayout]); var takePicture = /*#__PURE__*/function () { var _ref4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() { var captureOptions, photo, capturedAspectRatio, greenFrameCoords, _t2; return _regenerator().w(function (_context3) { while (1) switch (_context3.p = _context3.n) { case 0: if (hasPermission) { _context3.n = 1; break; } _reactNative.Alert.alert("Permission requise", "Veuillez autoriser l'accès à la caméra pour prendre une photo."); return _context3.a(2); case 1: if (!cameraRef.current) { _context3.n = 8; break; } _context3.p = 2; // Show loading after a delay (using setImmediate for iOS compatibility) waitForRender(5).then(function () { setLoadingBeforeCapture(true); }); // Wait a bit before taking picture (works on iOS) _context3.n = 3; return waitForRender(2); case 3: // ✅ OPTIMIZED: Capture with maximum quality and native camera ratio // Platform-specific optimizations for best quality captureOptions = _objectSpread(_objectSpread({ // Maximum quality (0-1, 1 = best quality, no compression) quality: 1, // Disable shutter sound for better UX shutterSound: false, // ✅ CRITICAL: skipProcessing preserves original resolution and avoids interpolation // This ensures pixel-perfect quality and prevents premature resizing // NOTE: Some Android devices are unstable with skipProcessing; keep it off there. skipProcessing: _reactNative.Platform.OS === 'ios', // Include EXIF metadata (orientation, camera settings, etc.) exif: true }, _reactNative.Platform.OS === 'ios' && { // iOS: Use native capture format (typically 4:3 or 16:9 depending on device) // No additional processing to preserve quality }), _reactNative.Platform.OS === 'android' && { // Android: Ensure maximum resolution capture // skipProcessing already handles this, but we can add Android-specific options if needed }); console.log("📸 Capturing photo with maximum quality settings:", { platform: _reactNative.Platform.OS, options: captureOptions, wrapperSize: { width: cameraWrapperLayout.width, height: cameraWrapperLayout.height } }); _context3.n = 4; return cameraRef.current.takePictureAsync(captureOptions); case 4: photo = _context3.v; if (!(!photo.width || !photo.height || photo.width === 0 || photo.height === 0)) { _context3.n = 5; break; } throw new Error("Invalid photo dimensions received from camera"); case 5: capturedAspectRatio = photo.width / photo.height; console.log("✅ Photo captured with maximum quality:", { uri: photo.uri, width: photo.width, height: photo.height, aspectRatio: capturedAspectRatio.toFixed(3), expectedRatio: "~1.33 (4:3) or ~1.78 (16:9)", exif: photo.exif ? "present" : "missing", fileSize: photo.uri ? "available" : "unknown" }); // ✅ CRITICAL FIX: Use the same green frame coordinates that are used for rendering // Fallback to recalculation if, for some reason, state is not yet set greenFrameCoords = greenFrame || calculateGreenFrameCoordinates(); if (greenFrameCoords) { _context3.n = 6; break; } throw new Error("Green frame coordinates not available"); case 6: // ✅ Send photo URI and frame data to ImageCropper // The photo maintains its native resolution and aspect ratio // ImageCropper will handle display and cropping while preserving quality onPhotoCaptured(photo.uri, { greenFrame: greenFrameCoords, capturedImageSize: { width: photo.width, height: photo.height, aspectRatio: capturedAspectRatio } }); setLoadingBeforeCapture(false); _context3.n = 8; break; case 7: _context3.p = 7; _t2 = _context3.v; console.error("❌ Error capturing photo:", _t2); setLoadingBeforeCapture(false); _reactNative.Alert.alert("Erreur", "Impossible de capturer la photo: ".concat(_t2.message || "Erreur inconnue", ". Veuillez r\xE9essayer.")); case 8: return _context3.a(2); } }, _callee3, null, [[2, 7]]); })); return function takePicture() { return _ref4.apply(this, arguments); }; }(); return /*#__PURE__*/_react["default"].createElement(_reactNative.SafeAreaView, { style: styles.outerContainer }, hasPermission === null && /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: styles.permissionContainer }, /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, { size: "large", color: PRIMARY_GREEN }), /*#__PURE__*/_react["default"].createElement(_reactNative.Text, { style: styles.permissionText }, "Demande d'autorisation cam\xE9ra...")), hasPermission === false && /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: styles.permissionContainer }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, { style: styles.permissionText }, "Autorisation cam\xE9ra refus\xE9e. Activez-la dans les param\xE8tres pour continuer."), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, { style: styles.permissionButton, onPress: requestPermissionAgain }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, { style: styles.permissionButtonText }, "R\xE9essayer"))), hasPermission === true && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: [styles.cameraWrapper, { width: cameraPreviewWidth }], ref: cameraWrapperRef, onLayout: function onLayout(e) { var layout = e.nativeEvent.layout; setCameraWrapperLayout({ width: layout.width, height: layout.height, x: layout.x, y: layout.y }); console.log("Camera wrapper layout updated:", layout); } }, /*#__PURE__*/_react["default"].createElement(_expoCamera.CameraView, { style: styles.camera, facing: "back", ref: cameraRef, pictureSize: pictureSize, onCameraReady: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() { var _cameraRef$current, _cameraRef$current$ge, sizes, bestSize, _t3; return _regenerator().w(function (_context4) { while (1) switch (_context4.p = _context4.n) { case 0: _context4.p = 0; setIsReady(false); _context4.n = 1; return (_cameraRef$current = cameraRef.current) === null || _cameraRef$current === void 0 || (_cameraRef$current$ge = _cameraRef$current.getAvailablePictureSizesAsync) === null || _cameraRef$current$ge === void 0 ? void 0 : _cameraRef$current$ge.call(_cameraRef$current); case 1: sizes = _context4.v; bestSize = chooseBestPictureSize(sizes || []); if (bestSize) { setPictureSize(bestSize); console.log("✅ Selected highest available picture size:", { preferredNativeSizes: preferredNativeSizes, bestSize: bestSize, availableSizes: sizes }); } else { console.log("ℹ️ No explicit picture size selected; using camera default"); } _context4.n = 3; break; case 2: _context4.p = 2; _t3 = _context4.v; console.warn("⚠️ Could not query picture sizes; using camera default:", _t3); case 3: _context4.p = 3; _context4.n = 4; return waitForRender(2); case 4: setIsReady(true); console.log("✅ Camera ready - Maximum quality capture enabled"); return _context4.f(3); case 5: return _context4.a(2); } }, _callee4, null, [[0, 2, 3, 5]]); })) // ✅ Ensure camera uses native aspect ratio (typically 4:3 for most devices) // The wrapper constrains the view, but capture uses full sensor resolution // This ensures preview matches what will be captured }), /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: [styles.topBar, styles.topBarCentered] }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, { style: [styles.topBarTitle, styles.topBarCenteredTitle] }, instructionText)), loadingBeforeCapture && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: styles.loadingOverlay }, /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, { size: "large", color: PRIMARY_GREEN })), /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: styles.touchBlocker, pointerEvents: "auto" })), greenFrame && /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: [styles.scanFrame, { left: greenFrame.x, top: greenFrame.y, width: greenFrame.width, height: greenFrame.height }] })), /*#__PURE__*/_react["default"].createElement(_reactNative.View, { style: [styles.buttonContainer, { bottom: ((insets === null || insets === void 0 ? void 0 : insets.bottom) || 0) + 12, marginBottom: 0 }] }, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, { style: [styles.captureButton, (!isReady || loadingBeforeCapture || !hasPermission) && { opacity: 0.55 }], onPress: takePicture, disabled: !isReady || loadingBeforeCapture || !hasPermission }, /*#__PURE__*/_react["default"].createElement(_vectorIcons.Ionicons, { name: "camera-outline", size: 26, color: "#121212" }))))); } var PRIMARY_GREEN = '#689F38'; var ACCENT_GREEN = '#689F38'; var DEEP_BLACK = '#060708'; var GLOW_WHITE = 'rgba(255, 255, 255, 0.9)'; var GLASS = 'rgba(20,22,26,0.9)'; var styles = _reactNative.StyleSheet.create({ outerContainer: { flex: 1, backgroundColor: DEEP_BLACK, justifyContent: 'center', alignItems: 'center' }, permissionContainer: { flex: 1, width: '100%', justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 }, permissionText: { marginTop: 12, fontSize: 14, color: GLOW_WHITE, textAlign: 'center' }, permissionButton: { marginTop: 16, backgroundColor: GLASS, paddingHorizontal: 16, paddingVertical: 10, borderRadius: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.12)' }, permissionButtonText: { color: 'white', fontWeight: '600' }, cameraWrapper: { aspectRatio: 9 / 16, borderRadius: 0, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', position: 'relative' }, cameraInstruction: { position: 'absolute', bottom: 170, left: 18, right: 18, textAlign: 'center', color: 'rgba(255,255,255,0.82)', fontSize: 13, fontWeight: '500', zIndex: 10, textShadowColor: 'rgba(0, 0, 0, 0.6)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, pointerEvents: 'none' }, camera: _objectSpread({}, _reactNative.StyleSheet.absoluteFillObject), scanFrame: { position: 'absolute', borderWidth: 2, borderColor: ACCENT_GREEN, borderRadius: 2, backgroundColor: 'rgba(0, 0, 0, 0.1)' }, loadingOverlay: _objectSpread(_objectSpread({}, _reactNative.StyleSheet.absoluteFillObject), {}, { backgroundColor: 'rgba(0, 0, 0, 0.5)', zIndex: 20, justifyContent: 'center', alignItems: 'center' }), touchBlocker: _objectSpread(_objectSpread({}, _reactNative.StyleSheet.absoluteFillObject), {}, { zIndex: 21, backgroundColor: 'transparent' }), buttonContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, marginBottom: 8, alignItems: 'center', justifyContent: 'center' }, captureButton: { marginTop: 10, width: 68, height: 68, borderRadius: 20, backgroundColor: ACCENT_GREEN, alignItems: 'center', justifyContent: 'center', shadowColor: ACCENT_GREEN, shadowOpacity: 0.42, shadowRadius: 10, shadowOffset: { width: 0, height: 2 }, elevation: 8 }, topBar: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 20, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 18 }, topBarCentered: { justifyContent: 'center', alignItems: 'center', paddingHorizontal: 0 }, topIconButton: { width: 36, height: 36, borderRadius: 12, backgroundColor: 'rgba(0,0,0,0.35)', alignItems: 'center', justifyContent: 'center' }, topBarTitle: { color: ACCENT_GREEN, fontSize: 30, fontWeight: '700', letterSpacing: 0.3 }, topBarCenteredTitle: { width: '100%', textAlign: 'center', paddingHorizontal: 18 }, detectBadge: { position: 'absolute', top: 76, left: 18, zIndex: 20, backgroundColor: 'rgba(40, 44, 50, 0.82)', borderRadius: 16, paddingHorizontal: 14, paddingVertical: 10 }, detectBadgeText: { color: 'rgba(255,255,255,0.78)', fontSize: 12, fontWeight: '700', letterSpacing: 1.6, textTransform: 'uppercase' }, cornerHandle: { position: 'absolute', width: 24, height: 24, borderRadius: 12, backgroundColor: '#DFFFD0', borderWidth: 2, borderColor: ACCENT_GREEN, shadowColor: ACCENT_GREEN, shadowOpacity: 0.55, shadowRadius: 10, shadowOffset: { width: 0, height: 0 }, elevation: 8, zIndex: 25 }, dockLabel: { marginTop: 10, marginBottom: 10, color: 'rgba(255,255,255,0.63)', textAlign: 'center', fontSize: 12, letterSpacing: 3.2, textTransform: 'uppercase' }, dock: { height: 76, width: '92%', borderRadius: 22, backgroundColor: 'rgba(27,30,35,0.92)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.08)', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-around', paddingHorizontal: 12 }, dockButton: { width: 44, height: 44, borderRadius: 14, alignItems: 'center', justifyContent: 'center' }, dockButtonActive: { width: 74, height: 52, borderRadius: 16, backgroundColor: ACCENT_GREEN, shadowColor: ACCENT_GREEN, shadowOpacity: 0.4, shadowRadius: 10, shadowOffset: { width: 0, height: 1 }, elevation: 8 } });