react-native-expo-cropper 1.2.38 → 1.2.39

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.
@@ -97,6 +97,9 @@ function CustomCamera(_ref) {
97
97
  // ✅ CRITICAL FIX: Calculate green frame coordinates relative to camera preview
98
98
  // The green frame should be calculated on the wrapper (as it's visually drawn there)
99
99
  // But we store it with wrapper dimensions so ImageCropper can map it correctly
100
+ //
101
+ // NOTE: Camera capture aspect ratio (typically 4:3) may differ from wrapper aspect ratio (9:16)
102
+ // This is handled in ImageCropper by using "cover" mode to match preview content
100
103
  var calculateGreenFrameCoordinates = function calculateGreenFrameCoordinates() {
101
104
  var wrapperWidth = cameraWrapperLayout.width;
102
105
  var wrapperHeight = cameraWrapperLayout.height;
@@ -139,12 +142,12 @@ function CustomCamera(_ref) {
139
142
  }, [cameraWrapperLayout]);
140
143
  var takePicture = /*#__PURE__*/function () {
141
144
  var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
142
- var photo, greenFrameCoords, fixedUri, _t;
145
+ var captureOptions, photo, capturedAspectRatio, greenFrameCoords, _t;
143
146
  return _regenerator().w(function (_context2) {
144
147
  while (1) switch (_context2.p = _context2.n) {
145
148
  case 0:
146
149
  if (!cameraRef.current) {
147
- _context2.n = 5;
150
+ _context2.n = 7;
148
151
  break;
149
152
  }
150
153
  _context2.p = 1;
@@ -157,54 +160,87 @@ function CustomCamera(_ref) {
157
160
  _context2.n = 2;
158
161
  return waitForRender(2);
159
162
  case 2:
160
- _context2.n = 3;
161
- return cameraRef.current.takePictureAsync({
163
+ // OPTIMIZED: Capture with maximum quality and native camera ratio
164
+ // Platform-specific optimizations for best quality
165
+ captureOptions = _objectSpread(_objectSpread({
166
+ // Maximum quality (0-1, 1 = best quality, no compression)
162
167
  quality: 1,
163
- // Qualité maximale (0-1, 1 = meilleure qualité)
168
+ // Disable shutter sound for better UX
164
169
  shutterSound: false,
165
- // skipProcessing: true = Désactiver le traitement automatique pour préserver la qualité pixel-perfect
166
- // IMPORTANT : Cela préserve la résolution originale et évite toute interpolation
170
+ // ✅ CRITICAL: skipProcessing preserves original resolution and avoids interpolation
171
+ // This ensures pixel-perfect quality and prevents premature resizing
167
172
  skipProcessing: true,
168
- // exif: true = Inclure les métadonnées EXIF (orientation, etc.)
169
- // L'orientation sera gérée dans ImageCropper si nécessaire via la fonction de rotation
173
+ // Include EXIF metadata (orientation, camera settings, etc.)
170
174
  exif: true
175
+ }, _reactNative.Platform.OS === 'ios' && {
176
+ // iOS: Use native capture format (typically 4:3 or 16:9 depending on device)
177
+ // No additional processing to preserve quality
178
+ }), _reactNative.Platform.OS === 'android' && {
179
+ // Android: Ensure maximum resolution capture
180
+ // skipProcessing already handles this, but we can add Android-specific options if needed
181
+ });
182
+ console.log("📸 Capturing photo with maximum quality settings:", {
183
+ platform: _reactNative.Platform.OS,
184
+ options: captureOptions,
185
+ wrapperSize: {
186
+ width: cameraWrapperLayout.width,
187
+ height: cameraWrapperLayout.height
188
+ }
171
189
  });
190
+ _context2.n = 3;
191
+ return cameraRef.current.takePictureAsync(captureOptions);
172
192
  case 3:
173
193
  photo = _context2.v;
174
- console.log("Photo captured with maximum quality:", {
194
+ if (!(!photo.width || !photo.height || photo.width === 0 || photo.height === 0)) {
195
+ _context2.n = 4;
196
+ break;
197
+ }
198
+ throw new Error("Invalid photo dimensions received from camera");
199
+ case 4:
200
+ capturedAspectRatio = photo.width / photo.height;
201
+ console.log("✅ Photo captured with maximum quality:", {
175
202
  uri: photo.uri,
176
203
  width: photo.width,
177
204
  height: photo.height,
178
- exif: photo.exif ? "present" : "missing"
205
+ aspectRatio: capturedAspectRatio.toFixed(3),
206
+ expectedRatio: "~1.33 (4:3) or ~1.78 (16:9)",
207
+ exif: photo.exif ? "present" : "missing",
208
+ fileSize: photo.uri ? "available" : "unknown"
179
209
  });
180
210
 
181
211
  // ✅ CRITICAL FIX: Use the same green frame coordinates that are used for rendering
182
212
  // Fallback to recalculation if, for some reason, state is not yet set
183
- greenFrameCoords = greenFrame || calculateGreenFrameCoordinates(); // ✅ REFACTORISATION : Utiliser directement l'URI de la photo
184
- // L'orientation sera gérée dans ImageCropper si l'utilisateur utilise la fonction de rotation
185
- // skipProcessing: true préserve la qualité mais peut laisser l'orientation EXIF non appliquée
186
- // C'est acceptable car l'utilisateur peut corriger via la rotation dans ImageCropper
187
- fixedUri = photo.uri; // Envoyer l'URI et les coordonnées du green frame à ImageCropper
188
- onPhotoCaptured(fixedUri, {
213
+ greenFrameCoords = greenFrame || calculateGreenFrameCoordinates();
214
+ if (greenFrameCoords) {
215
+ _context2.n = 5;
216
+ break;
217
+ }
218
+ throw new Error("Green frame coordinates not available");
219
+ case 5:
220
+ // ✅ Send photo URI and frame data to ImageCropper
221
+ // The photo maintains its native resolution and aspect ratio
222
+ // ImageCropper will handle display and cropping while preserving quality
223
+ onPhotoCaptured(photo.uri, {
189
224
  greenFrame: greenFrameCoords,
190
225
  capturedImageSize: {
191
226
  width: photo.width,
192
- height: photo.height
227
+ height: photo.height,
228
+ aspectRatio: capturedAspectRatio
193
229
  }
194
230
  });
195
231
  setLoadingBeforeCapture(false);
196
- _context2.n = 5;
232
+ _context2.n = 7;
197
233
  break;
198
- case 4:
199
- _context2.p = 4;
234
+ case 6:
235
+ _context2.p = 6;
200
236
  _t = _context2.v;
201
- console.error("Error capturing photo:", _t);
237
+ console.error("Error capturing photo:", _t);
202
238
  setLoadingBeforeCapture(false);
203
- _reactNative.Alert.alert("Erreur", "Impossible de capturer la photo. Veuillez réessayer.");
204
- case 5:
239
+ _reactNative.Alert.alert("Erreur", "Impossible de capturer la photo: ".concat(_t.message || "Erreur inconnue", ". Veuillez r\xE9essayer."));
240
+ case 7:
205
241
  return _context2.a(2);
206
242
  }
207
- }, _callee2, null, [[1, 4]]);
243
+ }, _callee2, null, [[1, 6]]);
208
244
  }));
209
245
  return function takePicture() {
210
246
  return _ref3.apply(this, arguments);
@@ -230,8 +266,12 @@ function CustomCamera(_ref) {
230
266
  facing: "back",
231
267
  ref: cameraRef,
232
268
  onCameraReady: function onCameraReady() {
233
- return setIsReady(true);
269
+ setIsReady(true);
270
+ console.log("✅ Camera ready - Maximum quality capture enabled");
234
271
  }
272
+ // ✅ Ensure camera uses native aspect ratio (typically 4:3 for most devices)
273
+ // The wrapper constrains the view, but capture uses full sensor resolution
274
+ // This ensures preview matches what will be captured
235
275
  }), loadingBeforeCapture && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
236
276
  style: styles.loadingOverlay
237
277
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
@@ -1299,10 +1299,124 @@ var ImageCropper = function ImageCropper(_ref) {
1299
1299
  onCancel: function onCancel() {
1300
1300
  return setShowCustomCamera(false);
1301
1301
  }
1302
- }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, !showResult && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1303
- style: image ? _ImageCropperStyles["default"].buttonContainer : _ImageCropperStyles["default"].centerButtonsContainer
1304
- }, image && _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1305
- style: _ImageCropperStyles["default"].iconButton,
1302
+ }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1303
+ style: {
1304
+ width: _reactNative.Dimensions.get('window').width,
1305
+ aspectRatio: 9 / 16,
1306
+ borderRadius: 30,
1307
+ overflow: 'hidden',
1308
+ alignItems: 'center',
1309
+ justifyContent: 'center',
1310
+ position: 'relative',
1311
+ backgroundColor: 'black',
1312
+ marginBottom: 0 // ✅ Les boutons sont maintenant en position absolue en bas
1313
+ },
1314
+ ref: commonWrapperRef,
1315
+ onLayout: onCommonWrapperLayout
1316
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1317
+ ref: viewRef,
1318
+ collapsable: false,
1319
+ style: _reactNative.StyleSheet.absoluteFill,
1320
+ onStartShouldSetResponder: function onStartShouldSetResponder() {
1321
+ return true;
1322
+ },
1323
+ onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
1324
+ // ✅ CRITICAL: Always capture movement when a point is selected
1325
+ // This ensures vertical movement is captured correctly
1326
+ if (selectedPointIndex.current !== null) {
1327
+ return true;
1328
+ }
1329
+ // ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
1330
+ // This is especially important for vertical movement which ScrollView tries to intercept
1331
+ // We return true for ANY movement to ensure we capture it before ScrollView
1332
+ var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1333
+ if (hasMovement && Math.abs(gestureState.dy) > 5) {
1334
+ console.log("🔄 Vertical movement detected in responder:", {
1335
+ dx: gestureState.dx.toFixed(2),
1336
+ dy: gestureState.dy.toFixed(2),
1337
+ selectedPoint: selectedPointIndex.current
1338
+ });
1339
+ }
1340
+ return true;
1341
+ },
1342
+ onResponderGrant: function onResponderGrant(e) {
1343
+ // ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
1344
+ // This ensures we capture all movement, especially vertical
1345
+ // Handle tap to select point if needed
1346
+ if (selectedPointIndex.current === null) {
1347
+ handleTap(e);
1348
+ }
1349
+ },
1350
+ onResponderStart: handleTap,
1351
+ onResponderMove: function onResponderMove(e) {
1352
+ // ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
1353
+ // This is called for every move event, ensuring vertical movement is captured
1354
+ // handleMove now uses incremental delta calculation which is more reliable
1355
+ handleMove(e);
1356
+ },
1357
+ onResponderRelease: handleRelease,
1358
+ onResponderTerminationRequest: function onResponderTerminationRequest() {
1359
+ // ✅ CRITICAL: Never allow termination when dragging a point
1360
+ // This prevents ScrollView from stealing the responder during vertical movement
1361
+ return selectedPointIndex.current === null;
1362
+ }
1363
+ // ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
1364
+ // Capture responder BEFORE parent ScrollView can intercept
1365
+ ,
1366
+ onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
1367
+ // Always capture start events
1368
+ return true;
1369
+ },
1370
+ onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
1371
+ // ✅ CRITICAL: Always capture movement events before parent ScrollView
1372
+ // This is essential for vertical movement which ScrollView tries to intercept
1373
+ // Especially important when a point is selected or when there's any movement
1374
+ if (selectedPointIndex.current !== null) {
1375
+ return true;
1376
+ }
1377
+ // ✅ CRITICAL: Capture movement BEFORE ScrollView can intercept
1378
+ // This ensures we get vertical movement even if ScrollView tries to steal it
1379
+ var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1380
+ return hasMovement;
1381
+ }
1382
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
1383
+ source: {
1384
+ uri: image
1385
+ },
1386
+ style: _ImageCropperStyles["default"].image,
1387
+ resizeMode: (_cameraFrameData$curr3 = cameraFrameData.current) !== null && _cameraFrameData$curr3 !== void 0 && _cameraFrameData$curr3.greenFrame ? 'cover' : 'contain',
1388
+ onLayout: onImageLayout
1389
+ }), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
1390
+ style: _ImageCropperStyles["default"].overlay,
1391
+ pointerEvents: "none"
1392
+ }, function () {
1393
+ // ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
1394
+ var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
1395
+ var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
1396
+ return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1397
+ d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
1398
+ fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
1399
+ fillRule: "evenodd"
1400
+ }), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1401
+ d: createPath(),
1402
+ fill: "transparent",
1403
+ stroke: "white",
1404
+ strokeWidth: 2
1405
+ }), !showResult && points.map(function (point, index) {
1406
+ return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
1407
+ key: index,
1408
+ cx: point.x,
1409
+ cy: point.y,
1410
+ r: 10,
1411
+ fill: "white"
1412
+ });
1413
+ }));
1414
+ }()))), !showResult && image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1415
+ style: [_ImageCropperStyles["default"].buttonContainerBelow, {
1416
+ paddingBottom: Math.max(insets.bottom, 16)
1417
+ }]
1418
+ }, _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1419
+ style: _ImageCropperStyles["default"].rotationButton,
1306
1420
  onPress: function onPress() {
1307
1421
  return enableRotation && rotatePreviewImage(90);
1308
1422
  },
@@ -1311,50 +1425,36 @@ var ImageCropper = function ImageCropper(_ref) {
1311
1425
  name: "sync",
1312
1426
  size: 24,
1313
1427
  color: "white"
1314
- }))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1428
+ })), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1315
1429
  style: _ImageCropperStyles["default"].button,
1316
1430
  onPress: handleReset
1317
1431
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1318
1432
  style: _ImageCropperStyles["default"].buttonText
1319
- }, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1433
+ }, "Reset")), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1320
1434
  style: _ImageCropperStyles["default"].button,
1321
1435
  onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1322
- var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, scaleX, scaleY, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
1436
+ var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
1323
1437
  return _regenerator().w(function (_context2) {
1324
1438
  while (1) switch (_context2.p = _context2.n) {
1325
1439
  case 0:
1326
1440
  setIsLoading(true);
1327
1441
  _context2.p = 1;
1328
1442
  console.log("=== Starting pixel-perfect metadata export (no bitmap crop on mobile) ===");
1329
-
1330
- // ✅ REFACTORISATION : Utiliser les dimensions stockées (plus efficace)
1331
1443
  actualImageWidth = originalImageDimensions.current.width;
1332
- actualImageHeight = originalImageDimensions.current.height; // Vérifier que les dimensions sont valides
1444
+ actualImageHeight = originalImageDimensions.current.height;
1333
1445
  if (!(actualImageWidth === 0 || actualImageHeight === 0)) {
1334
1446
  _context2.n = 2;
1335
1447
  break;
1336
1448
  }
1337
1449
  throw new Error("Original image dimensions not available. Please wait for image to load.");
1338
1450
  case 2:
1339
- console.log("Original image dimensions:", {
1340
- width: actualImageWidth,
1341
- height: actualImageHeight
1342
- });
1343
-
1344
- // ✅ CORRECTION : Utiliser le rectangle de contenu réel (contain)
1345
- // ✅ CRITICAL: Recalculate displayedContentRect if not available
1346
1451
  layout = displayedImageLayout.current;
1347
- console.log("🔍 Checking displayedContentRect before crop:", {
1348
- displayedContentRect: displayedContentRect.current,
1349
- displayedImageLayout: layout,
1350
- originalImageDimensions: originalImageDimensions.current
1351
- });
1352
1452
  if (layout.width > 0 && layout.height > 0) {
1353
1453
  updateDisplayedContentRect(layout.width, layout.height);
1354
1454
  }
1355
1455
  contentRect = displayedContentRect.current;
1356
1456
  displayedWidth = contentRect.width;
1357
- displayedHeight = contentRect.height; // Vérifier que les dimensions d'affichage sont valides
1457
+ displayedHeight = contentRect.height;
1358
1458
  if (!(displayedWidth === 0 || displayedHeight === 0)) {
1359
1459
  _context2.n = 4;
1360
1460
  break;
@@ -1363,8 +1463,6 @@ var ImageCropper = function ImageCropper(_ref) {
1363
1463
  _context2.n = 3;
1364
1464
  break;
1365
1465
  }
1366
- console.warn("⚠️ displayedContentRect not available, using displayedImageLayout as fallback");
1367
- // Use layout dimensions as fallback (assuming no letterboxing)
1368
1466
  contentRect = {
1369
1467
  x: layout.x,
1370
1468
  y: layout.y,
@@ -1373,21 +1471,12 @@ var ImageCropper = function ImageCropper(_ref) {
1373
1471
  };
1374
1472
  displayedWidth = contentRect.width;
1375
1473
  displayedHeight = contentRect.height;
1376
- // Update the ref for consistency
1377
1474
  displayedContentRect.current = contentRect;
1378
1475
  _context2.n = 4;
1379
1476
  break;
1380
1477
  case 3:
1381
- throw new Error("Displayed image dimensions not available. Image may not be laid out yet. Please wait a moment and try again.");
1478
+ throw new Error("Displayed image dimensions not available.");
1382
1479
  case 4:
1383
- console.log("✅ Using contentRect for crop:", contentRect);
1384
- console.log("Displayed image dimensions:", {
1385
- width: displayedWidth,
1386
- height: displayedHeight
1387
- });
1388
-
1389
- // ✅ CAMERA (cover mode): scale = max, image fills wrapper, has offset
1390
- // ✅ GALLERY (contain mode): scale = min, uniform
1391
1480
  isCoverMode = !!(cameraFrameData.current && cameraFrameData.current.greenFrame);
1392
1481
  coverOffsetX = 0, coverOffsetY = 0;
1393
1482
  if (isCoverMode) {
@@ -1396,43 +1485,16 @@ var ImageCropper = function ImageCropper(_ref) {
1396
1485
  scaledHeight = actualImageHeight * scale;
1397
1486
  coverOffsetX = (scaledWidth - displayedWidth) / 2;
1398
1487
  coverOffsetY = (scaledHeight - displayedHeight) / 2;
1399
- console.log("Scale factor (COVER mode for camera):", {
1400
- scale: scale,
1401
- coverOffsetX: coverOffsetX,
1402
- coverOffsetY: coverOffsetY
1403
- });
1404
1488
  } else {
1405
- scaleX = actualImageWidth / displayedWidth;
1406
- scaleY = actualImageHeight / displayedHeight;
1407
- if (Math.abs(scaleX - scaleY) > 0.01) {
1408
- console.warn("Scale mismatch detected! This may cause incorrect crop coordinates.", {
1409
- scaleX: scaleX,
1410
- scaleY: scaleY,
1411
- actualImageWidth: actualImageWidth,
1412
- actualImageHeight: actualImageHeight,
1413
- displayedWidth: displayedWidth,
1414
- displayedHeight: displayedHeight
1415
- });
1416
- }
1417
- scale = scaleX;
1418
- console.log("Scale factor (contain, uniform):", {
1419
- scale: scale,
1420
- contentRect: contentRect
1421
- });
1489
+ scale = actualImageWidth / displayedWidth;
1422
1490
  }
1423
1491
  originalUri = sourceImageUri.current || image;
1424
1492
  cropMeta = null;
1425
1493
  if (points.length > 0) {
1426
1494
  try {
1427
- console.log("Calculating crop boundaries from points...");
1428
- console.log("Points (display coordinates):", points);
1429
- console.log("Content rect (offsets):", contentRect);
1430
-
1431
- // ✅ Conversion display -> image px (contain or cover)
1432
1495
  imagePoints = points.map(function (point) {
1433
1496
  var clampedX, clampedY, origX, origY;
1434
1497
  if (isCoverMode) {
1435
- // Cover: display = wrapper, scale = max, image centered with offset
1436
1498
  clampedX = Math.max(0, Math.min(point.x, contentRect.width));
1437
1499
  clampedY = Math.max(0, Math.min(point.y, contentRect.height));
1438
1500
  origX = (clampedX + coverOffsetX) / scale;
@@ -1450,9 +1512,6 @@ var ImageCropper = function ImageCropper(_ref) {
1450
1512
  y: finalY
1451
1513
  };
1452
1514
  });
1453
- console.log("Converted image points (original coordinates):", imagePoints);
1454
-
1455
- // Calculer la bounding box : min X, min Y, max X, max Y
1456
1515
  minX = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1457
1516
  return p.x;
1458
1517
  })));
@@ -1464,36 +1523,20 @@ var ImageCropper = function ImageCropper(_ref) {
1464
1523
  })));
1465
1524
  maxY = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1466
1525
  return p.y;
1467
- }))); // ✅ CORRECTION : arrondi "conservateur" (floor origin + ceil end)
1468
- // évite de rogner des pixels et réduit le risque de crop plus petit (perte de détails).
1526
+ })));
1469
1527
  cropX = Math.max(0, Math.floor(minX));
1470
1528
  cropY = Math.max(0, Math.floor(minY));
1471
1529
  cropEndX = Math.min(actualImageWidth, Math.ceil(maxX));
1472
1530
  cropEndY = Math.min(actualImageHeight, Math.ceil(maxY));
1473
1531
  cropWidth = Math.max(0, cropEndX - cropX);
1474
1532
  cropHeight = Math.max(0, cropEndY - cropY);
1475
- console.log("Crop parameters (pixel-perfect):", {
1476
- x: cropX,
1477
- y: cropY,
1478
- width: cropWidth,
1479
- height: cropHeight,
1480
- imageWidth: actualImageWidth,
1481
- imageHeight: actualImageHeight,
1482
- boundingBox: {
1483
- minX: minX,
1484
- minY: minY,
1485
- maxX: maxX,
1486
- maxY: maxY
1487
- }
1488
- });
1489
1533
  if (cropWidth > 0 && cropHeight > 0) {
1490
- // 1) bbox in ORIGINAL image pixel coords
1491
1534
  bbox = {
1492
1535
  x: cropX,
1493
1536
  y: cropY,
1494
1537
  width: cropWidth,
1495
1538
  height: cropHeight
1496
- }; // 2) polygon points relative to bbox (still in ORIGINAL pixel grid)
1539
+ };
1497
1540
  polygon = imagePoints.map(function (point) {
1498
1541
  return {
1499
1542
  x: point.x - cropX,
@@ -1509,19 +1552,13 @@ var ImageCropper = function ImageCropper(_ref) {
1509
1552
  height: actualImageHeight
1510
1553
  }
1511
1554
  };
1512
- console.log("Crop meta ready:", cropMeta);
1513
- } else {
1514
- console.warn("Invalid crop dimensions, cannot export crop meta");
1515
1555
  }
1516
1556
  } catch (cropError) {
1517
1557
  console.error("Error computing crop meta:", cropError);
1518
1558
  }
1519
- } else {
1520
- console.log("No crop points defined, using original image");
1521
1559
  }
1522
1560
  name = "IMAGE XTK".concat(Date.now());
1523
1561
  if (onConfirm) {
1524
- console.log("Calling onConfirm with:", originalUri, name, cropMeta);
1525
1562
  onConfirm(originalUri, name, cropMeta);
1526
1563
  }
1527
1564
  _context2.n = 6;
@@ -1544,139 +1581,20 @@ var ImageCropper = function ImageCropper(_ref) {
1544
1581
  }))
1545
1582
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1546
1583
  style: _ImageCropperStyles["default"].buttonText
1547
- }, "Confirm"))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1548
- style: {
1549
- width: _reactNative.Dimensions.get('window').width,
1550
- aspectRatio: 9 / 16,
1551
- borderRadius: 30,
1552
- overflow: 'hidden',
1553
- alignItems: 'center',
1554
- justifyContent: 'center',
1555
- position: 'relative',
1556
- backgroundColor: 'black'
1557
- },
1558
- ref: commonWrapperRef,
1559
- onLayout: onCommonWrapperLayout
1560
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1561
- ref: viewRef,
1562
- collapsable: false,
1563
- style: _reactNative.StyleSheet.absoluteFill,
1564
- onStartShouldSetResponder: function onStartShouldSetResponder() {
1565
- return true;
1566
- },
1567
- onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
1568
- // ✅ CRITICAL: Always capture movement when a point is selected
1569
- // This ensures vertical movement is captured correctly
1570
- if (selectedPointIndex.current !== null) {
1571
- return true;
1572
- }
1573
- // ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
1574
- // This is especially important for vertical movement which ScrollView tries to intercept
1575
- // We return true for ANY movement to ensure we capture it before ScrollView
1576
- var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1577
- if (hasMovement && Math.abs(gestureState.dy) > 5) {
1578
- console.log("🔄 Vertical movement detected in responder:", {
1579
- dx: gestureState.dx.toFixed(2),
1580
- dy: gestureState.dy.toFixed(2),
1581
- selectedPoint: selectedPointIndex.current
1582
- });
1583
- }
1584
- return true;
1585
- },
1586
- onResponderGrant: function onResponderGrant(e) {
1587
- // ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
1588
- // This ensures we capture all movement, especially vertical
1589
- // Handle tap to select point if needed
1590
- if (selectedPointIndex.current === null) {
1591
- handleTap(e);
1592
- }
1593
- },
1594
- onResponderStart: handleTap,
1595
- onResponderMove: function onResponderMove(e) {
1596
- // ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
1597
- // This is called for every move event, ensuring vertical movement is captured
1598
- // handleMove now uses incremental delta calculation which is more reliable
1599
- handleMove(e);
1600
- },
1601
- onResponderRelease: handleRelease,
1602
- onResponderTerminationRequest: function onResponderTerminationRequest() {
1603
- // ✅ CRITICAL: Never allow termination when dragging a point
1604
- // This prevents ScrollView from stealing the responder during vertical movement
1605
- return selectedPointIndex.current === null;
1606
- }
1607
- // ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
1608
- // Capture responder BEFORE parent ScrollView can intercept
1609
- ,
1610
- onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
1611
- // Always capture start events
1612
- return true;
1613
- },
1614
- onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
1615
- // ✅ CRITICAL: Always capture movement events before parent ScrollView
1616
- // This is essential for vertical movement which ScrollView tries to intercept
1617
- // Especially important when a point is selected or when there's any movement
1618
- if (selectedPointIndex.current !== null) {
1619
- return true;
1620
- }
1621
- // Also capture if there's any movement to prevent ScrollView from intercepting
1622
- var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1623
- if (hasMovement && Math.abs(gestureState.dy) > 2) {
1624
- console.log("🔄 Capturing vertical movement before ScrollView:", {
1625
- dy: gestureState.dy.toFixed(2)
1626
- });
1627
- }
1628
- return hasMovement;
1629
- }
1630
- // ✅ CRITICAL: Prevent ScrollView from scrolling by stopping propagation
1631
- ,
1632
- onResponderReject: function onResponderReject() {
1633
- console.warn("⚠️ Responder rejected - ScrollView may intercept");
1634
- }
1635
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
1636
- source: {
1637
- uri: image
1638
- },
1639
- style: _ImageCropperStyles["default"].image,
1640
- resizeMode: (_cameraFrameData$curr3 = cameraFrameData.current) !== null && _cameraFrameData$curr3 !== void 0 && _cameraFrameData$curr3.greenFrame ? 'cover' : 'contain',
1641
- onLayout: onImageLayout
1642
- }), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
1643
- style: _ImageCropperStyles["default"].overlay,
1644
- pointerEvents: "none"
1645
- }, function () {
1646
- // ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
1647
- var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
1648
- var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
1649
- return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1650
- d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
1651
- fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
1652
- fillRule: "evenodd"
1653
- }), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1654
- d: createPath(),
1655
- fill: "transparent",
1656
- stroke: "white",
1657
- strokeWidth: 2
1658
- }), !showResult && points.map(function (point, index) {
1659
- return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
1660
- key: index,
1661
- cx: point.x,
1662
- cy: point.y,
1663
- r: 10,
1664
- fill: "white"
1665
- });
1666
- }));
1667
- }())))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1584
+ }, "Confirm"))), !showResult && !image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1585
+ style: _ImageCropperStyles["default"].centerButtonsContainer
1586
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1587
+ style: _ImageCropperStyles["default"].welcomeText
1588
+ }, "S\xE9lectionnez une image"))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1668
1589
  style: {
1669
1590
  position: 'absolute',
1670
1591
  left: -maskDimensions.width - 100,
1671
- // Hors écran mais pas trop loin
1672
1592
  top: -maskDimensions.height - 100,
1673
1593
  width: maskDimensions.width,
1674
1594
  height: maskDimensions.height,
1675
1595
  opacity: 1,
1676
- // Opacité normale pour la capture
1677
1596
  pointerEvents: 'none',
1678
1597
  zIndex: 9999,
1679
- // Z-index élevé pour s'assurer qu'elle est au-dessus
1680
1598
  overflow: 'hidden'
1681
1599
  },
1682
1600
  collapsable: false