react-native-expo-cropper 1.0.13 → 1.0.14

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.
@@ -35,6 +35,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
35
35
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
36
36
  var ImageCropper = function ImageCropper(_ref) {
37
37
  var onConfirm = _ref.onConfirm,
38
+ onCrop = _ref.onCrop,
38
39
  openCameraFirst = _ref.openCameraFirst,
39
40
  initialImage = _ref.initialImage,
40
41
  addheight = _ref.addheight;
@@ -97,29 +98,6 @@ var ImageCropper = function ImageCropper(_ref) {
97
98
  }
98
99
  });
99
100
  }, [image]);
100
- var initializeCropBox = function initializeCropBox() {
101
- var _imageMeasure$current = imageMeasure.current,
102
- width = _imageMeasure$current.width,
103
- height = _imageMeasure$current.height;
104
- // if (width === 0 || height === 0 || points.length > 0) return;
105
- var boxWidth = width * 0.6;
106
- var boxHeight = height * 0.6;
107
- var centerX = width / 2;
108
- var centerY = height / 2;
109
- setPoints([{
110
- x: centerX - boxWidth / 2,
111
- y: centerY - boxHeight / 2
112
- }, {
113
- x: centerX + boxWidth / 2,
114
- y: centerY - boxHeight / 2
115
- }, {
116
- x: centerX + boxWidth / 2,
117
- y: centerY + boxHeight / 2
118
- }, {
119
- x: centerX - boxWidth / 2,
120
- y: centerY + boxHeight / 2
121
- }]);
122
- };
123
101
  var onImageLayout = function onImageLayout(e) {
124
102
  var layout = e.nativeEvent.layout;
125
103
  imageMeasure.current = {
@@ -208,6 +186,31 @@ var ImageCropper = function ImageCropper(_ref) {
208
186
  // setPoints([]);
209
187
  initializeCropBox();
210
188
  };
189
+ var SCREEN_WIDTH = _reactNative.Dimensions.get('window').width;
190
+ var SCREEN_HEIGHT = _reactNative.Dimensions.get('window').height;
191
+ var initializeCropBox = function initializeCropBox() {
192
+ var _imageMeasure$current = imageMeasure.current,
193
+ width = _imageMeasure$current.width,
194
+ height = _imageMeasure$current.height;
195
+ // if (width === 0 || height === 0 || points.length > 0) return;
196
+ var boxWidth = width * 0.6;
197
+ var boxHeight = height * 0.6;
198
+ var centerX = width / 2;
199
+ var centerY = height / 2;
200
+ setPoints([{
201
+ x: centerX - boxWidth / 2,
202
+ y: centerY - boxHeight / 2
203
+ }, {
204
+ x: centerX + boxWidth / 2,
205
+ y: centerY - boxHeight / 2
206
+ }, {
207
+ x: centerX + boxWidth / 2,
208
+ y: centerY + boxHeight / 2
209
+ }, {
210
+ x: centerX - boxWidth / 2,
211
+ y: centerY + boxHeight / 2
212
+ }]);
213
+ };
211
214
  return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
212
215
  style: _ImageCropperStyles["default"].container
213
216
  }, showCustomCamera ? /*#__PURE__*/_react["default"].createElement(_CustomCamera["default"], {
@@ -227,52 +230,85 @@ var ImageCropper = function ImageCropper(_ref) {
227
230
  style: _ImageCropperStyles["default"].buttonText
228
231
  }, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
229
232
  style: _ImageCropperStyles["default"].button,
230
- onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
231
- var capturedUri, enhancedUri, name, _t;
232
- return _regenerator().w(function (_context) {
233
- while (1) switch (_context.p = _context.n) {
233
+ onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
234
+ var name, finalUri, capturePromise, timeoutPromise, callback, _t;
235
+ return _regenerator().w(function (_context2) {
236
+ while (1) switch (_context2.p = _context2.n) {
234
237
  case 0:
235
- // setShowFullScreenCapture(true);
236
- setIsLoading(true);
238
+ // Do NOT show loading modal during confirm to avoid UI stalls
237
239
  setShowResult(true);
238
- _context.p = 1;
239
- _context.n = 2;
240
+ if (viewRef.current) {
241
+ _context2.n = 1;
242
+ break;
243
+ }
244
+ console.error('View ref not ready for capture');
245
+ alert('Vue non prête pour la capture');
246
+ setShowResult(false);
247
+ return _context2.a(2);
248
+ case 1:
249
+ name = "IMAGE XTK".concat(Date.now(), ".png");
250
+ finalUri = null;
251
+ _context2.p = 2;
252
+ _context2.n = 3;
240
253
  return new Promise(function (resolve) {
241
254
  return requestAnimationFrame(resolve);
242
255
  });
243
- case 2:
244
- _context.n = 3;
245
- return (0, _reactNativeViewShot.captureRef)(viewRef, {
246
- format: 'png',
247
- quality: 1
248
- });
249
256
  case 3:
250
- capturedUri = _context.v;
251
- _context.n = 4;
252
- return (0, _ImageProcessor.enhanceImage)(capturedUri, addheight);
257
+ _context2.n = 4;
258
+ return new Promise(function (resolve) {
259
+ return _reactNative.InteractionManager.runAfterInteractions(resolve);
260
+ });
253
261
  case 4:
254
- enhancedUri = _context.v;
255
- name = "IMAGE XTK".concat(Date.now(), ".png");
256
- if (onConfirm) {
257
- onConfirm(enhancedUri, name);
258
- }
259
- _context.n = 6;
260
- break;
262
+ capturePromise = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
263
+ var capturedUri;
264
+ return _regenerator().w(function (_context) {
265
+ while (1) switch (_context.n) {
266
+ case 0:
267
+ _context.n = 1;
268
+ return (0, _reactNativeViewShot.captureRef)(viewRef.current, {
269
+ format: 'png',
270
+ quality: 1,
271
+ result: 'tmpfile'
272
+ });
273
+ case 1:
274
+ capturedUri = _context.v;
275
+ return _context.a(2, (0, _ImageProcessor.enhanceImage)(capturedUri, addheight));
276
+ }
277
+ }, _callee);
278
+ }))();
279
+ timeoutPromise = new Promise(function (_, reject) {
280
+ return setTimeout(function () {
281
+ return reject(new Error('Capture timed out'));
282
+ }, 7000);
283
+ });
284
+ _context2.n = 5;
285
+ return Promise.race([capturePromise, timeoutPromise]);
261
286
  case 5:
262
- _context.p = 5;
263
- _t = _context.v;
264
- console.error("Erreur lors de la capture :", _t);
265
- alert("Erreur lors de la capture !");
287
+ finalUri = _context2.v;
288
+ _context2.n = 7;
289
+ break;
266
290
  case 6:
267
- _context.p = 6;
291
+ _context2.p = 6;
292
+ _t = _context2.v;
293
+ console.error('Erreur lors de la capture :', _t);
294
+ alert('Erreur lors de la capture !');
295
+ case 7:
296
+ _context2.p = 7;
268
297
  setShowResult(false);
269
298
  setIsLoading(false);
270
299
  setShowFullScreenCapture(false);
271
- return _context.f(6);
272
- case 7:
273
- return _context.a(2);
300
+ return _context2.f(7);
301
+ case 8:
302
+ callback = onConfirm || (typeof onCrop === 'function' ? onCrop : null);
303
+ if (callback && finalUri) {
304
+ setTimeout(function () {
305
+ return callback(finalUri, name);
306
+ }, 0);
307
+ }
308
+ case 9:
309
+ return _context2.a(2);
274
310
  }
275
- }, _callee, null, [[1, 5, 6, 7]]);
311
+ }, _callee2, null, [[2, 6, 7, 8]]);
276
312
  }))
277
313
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
278
314
  style: _ImageCropperStyles["default"].buttonText
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-expo-cropper",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Recadrage polygonal d'images.",
5
5
  "main": "index.js",
6
6
  "author": "PCS AGRI",
@@ -1,12 +1,12 @@
1
1
  import styles from './ImageCropperStyles';
2
2
  import React, { useState, useRef, useEffect } from 'react';
3
- import { Modal,View, Image, Dimensions, TouchableOpacity, Animated, Text } from 'react-native';
3
+ import { Modal,View, Image, Dimensions, TouchableOpacity, Animated, Text, InteractionManager } from 'react-native';
4
4
  import Svg, { Path, Circle } from 'react-native-svg';
5
5
  import { captureRef } from 'react-native-view-shot';
6
6
  import CustomCamera from './CustomCamera';
7
7
  import { enhanceImage } from './ImageProcessor';
8
8
 
9
- const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) => {
9
+ const ImageCropper = ({ onConfirm, onCrop, openCameraFirst, initialImage ,addheight}) => {
10
10
 
11
11
 
12
12
  const [image, setImage] = useState(null);
@@ -56,23 +56,6 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
56
56
  });
57
57
  }, [image]);
58
58
 
59
-
60
- const initializeCropBox = () => {
61
- const { width, height } = imageMeasure.current;
62
- // if (width === 0 || height === 0 || points.length > 0) return;
63
- const boxWidth = width * 0.6;
64
- const boxHeight = height * 0.6;
65
- const centerX = width / 2;
66
- const centerY = height / 2;
67
- setPoints([
68
- { x: centerX - boxWidth / 2, y: centerY - boxHeight / 2 },
69
- { x: centerX + boxWidth / 2, y: centerY - boxHeight / 2 },
70
- { x: centerX + boxWidth / 2, y: centerY + boxHeight / 2 },
71
- { x: centerX - boxWidth / 2, y: centerY + boxHeight / 2 },
72
- ]);
73
- };
74
-
75
-
76
59
  const onImageLayout = (e) => {
77
60
  const layout = e.nativeEvent.layout;
78
61
  imageMeasure.current = {
@@ -95,7 +78,7 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
95
78
  if (!image || showResult) return;
96
79
  const now = Date.now();
97
80
  const { locationX: tapX, locationY: tapY } = e.nativeEvent;
98
-
81
+
99
82
  if (lastTap.current && now - lastTap.current < 300) {
100
83
  const exists = points.some(p => Math.abs(p.x - tapX) < 15 && Math.abs(p.y - tapY) < 15);
101
84
  if (!exists) setPoints([...points, { x: tapX, y: tapY }]);
@@ -112,43 +95,30 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
112
95
 
113
96
  const handleMove = (e) => {
114
97
  if (showResult || selectedPointIndex.current === null) return;
115
-
116
98
  const { locationX: moveX, locationY: moveY } = e.nativeEvent;
117
99
  const width = imageMeasure.current.width;
118
100
  const height = imageMeasure.current.height;
119
-
101
+
120
102
  const boundedX = Math.max(0, Math.min(moveX, width));
121
103
  const boundedY = Math.max(0, Math.min(moveY, height));
122
-
104
+
123
105
  const edgeThreshold = 10;
124
- const isNearTopOrBottomEdge =
125
- boundedY <= edgeThreshold || boundedY >= height - edgeThreshold;
126
-
127
- const isNearLeftOrRightEdge =
128
- boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
129
-
106
+ const isNearTopOrBottomEdge = boundedY <= edgeThreshold || boundedY >= height - edgeThreshold;
107
+ const isNearLeftOrRightEdge = boundedX <= edgeThreshold || boundedX >= width - edgeThreshold;
108
+
130
109
  if (isNearTopOrBottomEdge || isNearLeftOrRightEdge) {
131
110
  // Reset point to last known position
132
111
  if (lastValidPosition.current && selectedPointIndex.current !== null) {
133
- setPoints(prev =>
134
- prev.map((p, i) =>
135
- i === selectedPointIndex.current ? lastValidPosition.current : p
136
- )
137
- );
112
+ setPoints(prev => prev.map((p, i) => (i === selectedPointIndex.current ? lastValidPosition.current : p)));
138
113
  }
139
114
  selectedPointIndex.current = null;
140
115
  return;
141
116
  }
142
-
117
+
143
118
  // Valid move — update point and store as new last valid position
144
119
  const updatedPoint = { x: boundedX, y: boundedY };
145
120
  lastValidPosition.current = updatedPoint;
146
-
147
- setPoints(prev =>
148
- prev.map((p, i) =>
149
- i === selectedPointIndex.current ? updatedPoint : p
150
- )
151
- );
121
+ setPoints(prev => prev.map((p, i) => (i === selectedPointIndex.current ? updatedPoint : p)));
152
122
  };
153
123
 
154
124
  const handleRelease = () => {
@@ -161,6 +131,26 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
161
131
  };
162
132
 
163
133
 
134
+ const SCREEN_WIDTH = Dimensions.get('window').width;
135
+ const SCREEN_HEIGHT = Dimensions.get('window').height;
136
+
137
+ const initializeCropBox = () => {
138
+ const { width, height } = imageMeasure.current;
139
+ // if (width === 0 || height === 0 || points.length > 0) return;
140
+ const boxWidth = width * 0.6;
141
+ const boxHeight = height * 0.6;
142
+ const centerX = width / 2;
143
+ const centerY = height / 2;
144
+
145
+ setPoints([
146
+ { x: centerX - boxWidth / 2, y: centerY - boxHeight / 2 },
147
+ { x: centerX + boxWidth / 2, y: centerY - boxHeight / 2 },
148
+ { x: centerX + boxWidth / 2, y: centerY + boxHeight / 2 },
149
+ { x: centerX - boxWidth / 2, y: centerY + boxHeight / 2 },
150
+ ]);
151
+ };
152
+
153
+
164
154
  return (
165
155
  <View style={styles.container}>
166
156
 
@@ -183,33 +173,51 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage ,addheight}) =>
183
173
  <TouchableOpacity
184
174
  style={styles.button}
185
175
  onPress={async () => {
186
- // setShowFullScreenCapture(true);
187
- setIsLoading(true);
176
+ // Do NOT show loading modal during confirm to avoid UI stalls
188
177
  setShowResult(true);
178
+
179
+ if (!viewRef.current) {
180
+ console.error('View ref not ready for capture');
181
+ alert('Vue non prête pour la capture');
182
+ setShowResult(false);
183
+ return;
184
+ }
185
+
186
+ const name = `IMAGE XTK${Date.now()}.png`;
187
+ let finalUri = null;
189
188
  try {
189
+ // Yield UI work completely, then perform capture
190
190
  await new Promise((resolve) => requestAnimationFrame(resolve));
191
- const capturedUri = await captureRef(viewRef, {
192
- format: 'png',
193
- quality: 1,
194
- });
191
+ await new Promise((resolve) => InteractionManager.runAfterInteractions(resolve));
195
192
 
196
-
197
- const enhancedUri = await enhanceImage(capturedUri ,addheight);
198
- const name = `IMAGE XTK${Date.now()}.png`;
193
+ const capturePromise = (async () => {
194
+ const capturedUri = await captureRef(viewRef.current, {
195
+ format: 'png',
196
+ quality: 1,
197
+ result: 'tmpfile',
198
+ });
199
+ return enhanceImage(capturedUri, addheight);
200
+ })();
201
+
202
+ const timeoutPromise = new Promise((_, reject) =>
203
+ setTimeout(() => reject(new Error('Capture timed out')), 7000)
204
+ );
205
+
206
+ finalUri = await Promise.race([capturePromise, timeoutPromise]);
207
+ } catch (error) {
208
+ console.error('Erreur lors de la capture :', error);
209
+ alert('Erreur lors de la capture !');
210
+ } finally {
211
+ setShowResult(false);
212
+ setIsLoading(false);
213
+ setShowFullScreenCapture(false);
214
+ }
199
215
 
200
-
201
- if (onConfirm) {
202
- onConfirm(enhancedUri, name);
203
- }
204
- } catch (error) {
205
- console.error("Erreur lors de la capture :", error);
206
- alert("Erreur lors de la capture !");
207
- } finally {
208
- setShowResult(false);
209
- setIsLoading(false);
210
- setShowFullScreenCapture(false);
211
- }
212
- }}
216
+ const callback = onConfirm || (typeof onCrop === 'function' ? onCrop : null);
217
+ if (callback && finalUri) {
218
+ setTimeout(() => callback(finalUri, name), 0);
219
+ }
220
+ }}
213
221
  >
214
222
  <Text style={styles.buttonText}>Confirm</Text>
215
223
  </TouchableOpacity>