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 +19 -0
- package/dist/CustomCamera.js +116 -52
- package/dist/ImageCropper.js +118 -182
- package/dist/ImageCropperStyles.js +26 -16
- package/package.json +2 -2
- package/src/CustomCamera.js +125 -72
- package/src/ImageCropper.js +153 -181
- package/src/ImageCropperStyles.js +30 -19
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';
|
package/dist/CustomCamera.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
154
|
-
var captureOptions, photo, capturedAspectRatio, greenFrameCoords,
|
|
155
|
-
return _regenerator().w(function (
|
|
156
|
-
while (1) switch (
|
|
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
|
-
|
|
188
|
+
_context3.n = 8;
|
|
160
189
|
break;
|
|
161
190
|
}
|
|
162
|
-
|
|
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
|
-
|
|
198
|
+
_context3.n = 3;
|
|
170
199
|
return waitForRender(2);
|
|
171
|
-
case
|
|
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
|
-
|
|
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
|
-
|
|
229
|
+
_context3.n = 4;
|
|
200
230
|
return cameraRef.current.takePictureAsync(captureOptions);
|
|
201
|
-
case
|
|
202
|
-
photo =
|
|
231
|
+
case 4:
|
|
232
|
+
photo = _context3.v;
|
|
203
233
|
if (!(!photo.width || !photo.height || photo.width === 0 || photo.height === 0)) {
|
|
204
|
-
|
|
234
|
+
_context3.n = 5;
|
|
205
235
|
break;
|
|
206
236
|
}
|
|
207
237
|
throw new Error("Invalid photo dimensions received from camera");
|
|
208
|
-
case
|
|
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
|
-
|
|
254
|
+
_context3.n = 6;
|
|
225
255
|
break;
|
|
226
256
|
}
|
|
227
257
|
throw new Error("Green frame coordinates not available");
|
|
228
|
-
case
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
282
|
+
}, _callee3, null, [[2, 7]]);
|
|
253
283
|
}));
|
|
254
284
|
return function takePicture() {
|
|
255
|
-
return
|
|
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.
|
|
290
|
-
|
|
291
|
-
|
|
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'
|