react-native-expo-cropper
Version:
Recadrage polygonal d'images.
701 lines (689 loc) • 32.2 kB
JavaScript
"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
}
});