react-native-expo-cropper 1.2.47 → 1.2.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.MD CHANGED
@@ -32,6 +32,25 @@ EXPO SDK 54 -------------------------------------------------------------
32
32
  },
33
33
 
34
34
 
35
+ ## ✅ IMPORTANT (APK / production build)
36
+
37
+ Expo Go already contains native camera modules/permissions. In a **standalone APK** you must ensure the native config is present.
38
+
39
+ Add this to your `app.json` / `app.config.js` (example):
40
+
41
+ ```json
42
+ {
43
+ "expo": {
44
+ "plugins": ["expo-camera", "expo-image-picker", "expo-media-library"],
45
+ "android": {
46
+ "permissions": ["CAMERA"]
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ Then rebuild the APK (EAS / prebuild). If the camera crashes only in release, it’s almost always missing permissions/plugin config or a device-specific camera processing issue.
53
+
35
54
  ------------------------------ DO THIS IN YOUR APP SCREEN CODE !!!!!!!!!! :-------------------------------
36
55
 
37
56
  import ImageCropper from 'react-native-expo-cropper/src/ImageCropper';
@@ -9,7 +9,7 @@ var _react = _interopRequireWildcard(require("react"));
9
9
  var _reactNative = require("react-native");
10
10
  var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
11
11
  var _expoCamera = require("expo-camera");
12
- 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 _t2 in e) "default" !== _t2 && {}.hasOwnProperty.call(e, _t2) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t2)) && (i.get || i.set) ? o(f, _t2, i) : f[_t2] = e[_t2]); return f; })(e, t); }
12
+ 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 _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); }
13
13
  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; }
14
14
  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; }
15
15
  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; }
@@ -45,28 +45,22 @@ function CustomCamera(_ref) {
45
45
  _useState6 = _slicedToArray(_useState5, 2),
46
46
  hasPermission = _useState6[0],
47
47
  setHasPermission = _useState6[1];
48
- // Force a fresh native CameraView instance per "open" to avoid stale/cached preview.
49
- var _useState7 = (0, _react.useState)(function () {
50
- return "".concat(Date.now(), "-").concat(Math.random().toString(16).slice(2));
51
- }),
52
- _useState8 = _slicedToArray(_useState7, 1),
53
- cameraViewMountKey = _useState8[0];
54
48
  var cameraRef = (0, _react.useRef)(null);
55
49
  var cameraWrapperRef = (0, _react.useRef)(null);
56
50
  var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
57
- var _useState9 = (0, _react.useState)({
51
+ var _useState7 = (0, _react.useState)({
58
52
  width: 0,
59
53
  height: 0,
60
54
  x: 0,
61
55
  y: 0
62
56
  }),
57
+ _useState8 = _slicedToArray(_useState7, 2),
58
+ cameraWrapperLayout = _useState8[0],
59
+ setCameraWrapperLayout = _useState8[1];
60
+ var _useState9 = (0, _react.useState)(null),
63
61
  _useState0 = _slicedToArray(_useState9, 2),
64
- cameraWrapperLayout = _useState0[0],
65
- setCameraWrapperLayout = _useState0[1];
66
- var _useState1 = (0, _react.useState)(null),
67
- _useState10 = _slicedToArray(_useState1, 2),
68
- greenFrame = _useState10[0],
69
- setGreenFrame = _useState10[1];
62
+ greenFrame = _useState0[0],
63
+ setGreenFrame = _useState0[1];
70
64
  (0, _react.useEffect)(function () {
71
65
  _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
72
66
  var _yield$Camera$request, status;
@@ -85,6 +79,34 @@ function CustomCamera(_ref) {
85
79
  }, _callee);
86
80
  }))();
87
81
  }, []);
82
+ var requestPermissionAgain = /*#__PURE__*/function () {
83
+ var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
84
+ var _yield$Camera$request2, status, _t;
85
+ return _regenerator().w(function (_context2) {
86
+ while (1) switch (_context2.p = _context2.n) {
87
+ case 0:
88
+ _context2.p = 0;
89
+ _context2.n = 1;
90
+ return _expoCamera.Camera.requestCameraPermissionsAsync();
91
+ case 1:
92
+ _yield$Camera$request2 = _context2.v;
93
+ status = _yield$Camera$request2.status;
94
+ setHasPermission(status === 'granted');
95
+ _context2.n = 3;
96
+ break;
97
+ case 2:
98
+ _context2.p = 2;
99
+ _t = _context2.v;
100
+ setHasPermission(false);
101
+ case 3:
102
+ return _context2.a(2);
103
+ }
104
+ }, _callee2, null, [[0, 2]]);
105
+ }));
106
+ return function requestPermissionAgain() {
107
+ return _ref3.apply(this, arguments);
108
+ };
109
+ }();
88
110
 
89
111
  // Helper function to wait for multiple render cycles (works on iOS)
90
112
  var waitForRender = function waitForRender() {
@@ -150,25 +172,32 @@ function CustomCamera(_ref) {
150
172
  }
151
173
  }, [cameraWrapperLayout]);
152
174
  var takePicture = /*#__PURE__*/function () {
153
- var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
154
- var captureOptions, photo, capturedAspectRatio, greenFrameCoords, _t;
155
- return _regenerator().w(function (_context2) {
156
- while (1) switch (_context2.p = _context2.n) {
175
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
176
+ var captureOptions, photo, capturedAspectRatio, greenFrameCoords, _t2;
177
+ return _regenerator().w(function (_context3) {
178
+ while (1) switch (_context3.p = _context3.n) {
157
179
  case 0:
180
+ if (hasPermission) {
181
+ _context3.n = 1;
182
+ break;
183
+ }
184
+ _reactNative.Alert.alert("Permission requise", "Veuillez autoriser l'accès à la caméra pour prendre une photo.");
185
+ return _context3.a(2);
186
+ case 1:
158
187
  if (!cameraRef.current) {
159
- _context2.n = 7;
188
+ _context3.n = 8;
160
189
  break;
161
190
  }
162
- _context2.p = 1;
191
+ _context3.p = 2;
163
192
  // Show loading after a delay (using setImmediate for iOS compatibility)
164
193
  waitForRender(5).then(function () {
165
194
  setLoadingBeforeCapture(true);
166
195
  });
167
196
 
168
197
  // Wait a bit before taking picture (works on iOS)
169
- _context2.n = 2;
198
+ _context3.n = 3;
170
199
  return waitForRender(2);
171
- case 2:
200
+ case 3:
172
201
  // ✅ OPTIMIZED: Capture with maximum quality and native camera ratio
173
202
  // Platform-specific optimizations for best quality
174
203
  captureOptions = _objectSpread(_objectSpread({
@@ -178,7 +207,8 @@ function CustomCamera(_ref) {
178
207
  shutterSound: false,
179
208
  // ✅ CRITICAL: skipProcessing preserves original resolution and avoids interpolation
180
209
  // This ensures pixel-perfect quality and prevents premature resizing
181
- skipProcessing: true,
210
+ // NOTE: Some Android devices are unstable with skipProcessing; keep it off there.
211
+ skipProcessing: _reactNative.Platform.OS === 'ios',
182
212
  // Include EXIF metadata (orientation, camera settings, etc.)
183
213
  exif: true
184
214
  }, _reactNative.Platform.OS === 'ios' && {
@@ -196,16 +226,16 @@ function CustomCamera(_ref) {
196
226
  height: cameraWrapperLayout.height
197
227
  }
198
228
  });
199
- _context2.n = 3;
229
+ _context3.n = 4;
200
230
  return cameraRef.current.takePictureAsync(captureOptions);
201
- case 3:
202
- photo = _context2.v;
231
+ case 4:
232
+ photo = _context3.v;
203
233
  if (!(!photo.width || !photo.height || photo.width === 0 || photo.height === 0)) {
204
- _context2.n = 4;
234
+ _context3.n = 5;
205
235
  break;
206
236
  }
207
237
  throw new Error("Invalid photo dimensions received from camera");
208
- case 4:
238
+ case 5:
209
239
  capturedAspectRatio = photo.width / photo.height;
210
240
  console.log("✅ Photo captured with maximum quality:", {
211
241
  uri: photo.uri,
@@ -221,11 +251,11 @@ function CustomCamera(_ref) {
221
251
  // Fallback to recalculation if, for some reason, state is not yet set
222
252
  greenFrameCoords = greenFrame || calculateGreenFrameCoordinates();
223
253
  if (greenFrameCoords) {
224
- _context2.n = 5;
254
+ _context3.n = 6;
225
255
  break;
226
256
  }
227
257
  throw new Error("Green frame coordinates not available");
228
- case 5:
258
+ case 6:
229
259
  // ✅ Send photo URI and frame data to ImageCropper
230
260
  // The photo maintains its native resolution and aspect ratio
231
261
  // ImageCropper will handle display and cropping while preserving quality
@@ -238,26 +268,42 @@ function CustomCamera(_ref) {
238
268
  }
239
269
  });
240
270
  setLoadingBeforeCapture(false);
241
- _context2.n = 7;
271
+ _context3.n = 8;
242
272
  break;
243
- case 6:
244
- _context2.p = 6;
245
- _t = _context2.v;
246
- console.error("❌ Error capturing photo:", _t);
247
- setLoadingBeforeCapture(false);
248
- _reactNative.Alert.alert("Erreur", "Impossible de capturer la photo: ".concat(_t.message || "Erreur inconnue", ". Veuillez r\xE9essayer."));
249
273
  case 7:
250
- return _context2.a(2);
274
+ _context3.p = 7;
275
+ _t2 = _context3.v;
276
+ console.error("❌ Error capturing photo:", _t2);
277
+ setLoadingBeforeCapture(false);
278
+ _reactNative.Alert.alert("Erreur", "Impossible de capturer la photo: ".concat(_t2.message || "Erreur inconnue", ". Veuillez r\xE9essayer."));
279
+ case 8:
280
+ return _context3.a(2);
251
281
  }
252
- }, _callee2, null, [[1, 6]]);
282
+ }, _callee3, null, [[2, 7]]);
253
283
  }));
254
284
  return function takePicture() {
255
- return _ref3.apply(this, arguments);
285
+ return _ref4.apply(this, arguments);
256
286
  };
257
287
  }();
258
288
  return /*#__PURE__*/_react["default"].createElement(_reactNative.SafeAreaView, {
259
289
  style: styles.outerContainer
260
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
290
+ }, hasPermission === null && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
291
+ style: styles.permissionContainer
292
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, {
293
+ size: "large",
294
+ color: PRIMARY_GREEN
295
+ }), /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
296
+ style: styles.permissionText
297
+ }, "Demande d'autorisation cam\xE9ra...")), hasPermission === false && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
298
+ style: styles.permissionContainer
299
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
300
+ style: styles.permissionText
301
+ }, "Autorisation cam\xE9ra refus\xE9e. Activez-la dans les param\xE8tres pour continuer."), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
302
+ style: styles.permissionButton,
303
+ onPress: requestPermissionAgain
304
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
305
+ style: styles.permissionButtonText
306
+ }, "R\xE9essayer"))), hasPermission === true && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
261
307
  style: [styles.cameraWrapper, {
262
308
  width: cameraPreviewWidth
263
309
  }],
@@ -273,7 +319,6 @@ function CustomCamera(_ref) {
273
319
  console.log("Camera wrapper layout updated:", layout);
274
320
  }
275
321
  }, /*#__PURE__*/_react["default"].createElement(_expoCamera.CameraView, {
276
- key: cameraViewMountKey,
277
322
  style: styles.camera,
278
323
  facing: "back",
279
324
  ref: cameraRef,
@@ -286,10 +331,9 @@ function CustomCamera(_ref) {
286
331
  // This ensures preview matches what will be captured
287
332
  }), loadingBeforeCapture && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
288
333
  style: styles.loadingOverlay
289
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
290
- source: require('../src/assets/loadingCamera.gif'),
291
- style: styles.loadingGif,
292
- resizeMode: "contain"
334
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.ActivityIndicator, {
335
+ size: "large",
336
+ color: PRIMARY_GREEN
293
337
  })), /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
294
338
  style: styles.touchBlocker,
295
339
  pointerEvents: "auto"
@@ -308,8 +352,8 @@ function CustomCamera(_ref) {
308
352
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
309
353
  style: styles.button,
310
354
  onPress: takePicture,
311
- disabled: !isReady || loadingBeforeCapture
312
- })));
355
+ disabled: !isReady || loadingBeforeCapture || !hasPermission
356
+ }))));
313
357
  }
314
358
  var PRIMARY_GREEN = '#198754';
315
359
  var DEEP_BLACK = '#0B0B0B';
@@ -321,6 +365,30 @@ var styles = _reactNative.StyleSheet.create({
321
365
  justifyContent: 'center',
322
366
  alignItems: 'center'
323
367
  },
368
+ permissionContainer: {
369
+ flex: 1,
370
+ width: '100%',
371
+ justifyContent: 'center',
372
+ alignItems: 'center',
373
+ paddingHorizontal: 24
374
+ },
375
+ permissionText: {
376
+ marginTop: 12,
377
+ fontSize: 14,
378
+ color: GLOW_WHITE,
379
+ textAlign: 'center'
380
+ },
381
+ permissionButton: {
382
+ marginTop: 16,
383
+ backgroundColor: PRIMARY_GREEN,
384
+ paddingHorizontal: 16,
385
+ paddingVertical: 10,
386
+ borderRadius: 8
387
+ },
388
+ permissionButtonText: {
389
+ color: 'white',
390
+ fontWeight: '600'
391
+ },
324
392
  cameraWrapper: {
325
393
  aspectRatio: 9 / 16,
326
394
  borderRadius: 30,
@@ -343,10 +411,6 @@ var styles = _reactNative.StyleSheet.create({
343
411
  justifyContent: 'center',
344
412
  alignItems: 'center'
345
413
  }),
346
- loadingGif: {
347
- width: 100,
348
- height: 100
349
- },
350
414
  touchBlocker: _objectSpread(_objectSpread({}, _reactNative.StyleSheet.absoluteFillObject), {}, {
351
415
  zIndex: 21,
352
416
  backgroundColor: 'transparent'