@technotoil/image-video-editor 0.1.0 → 0.1.2

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.
Files changed (63) hide show
  1. package/README.md +17 -3
  2. package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +2 -6
  3. package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +75 -35
  4. package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +51 -35
  5. package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +98 -117
  6. package/ios/RNMediaEditor.m +38 -7
  7. package/ios/RNMediaLibrary.m +19 -15
  8. package/ios/RNVideoPreviewManager.m +2 -0
  9. package/lib/commonjs/assets/frames/film_vintage.png +0 -0
  10. package/lib/commonjs/assets/frames/floral_gold.png +0 -0
  11. package/lib/commonjs/assets/frames/minimal_double.png +0 -0
  12. package/lib/commonjs/assets/frames/polaroid_white.png +0 -0
  13. package/lib/commonjs/assets/frames/watercolor_floral.png +0 -0
  14. package/lib/commonjs/components/VideoEditor.js +235 -0
  15. package/lib/commonjs/components/VideoEditor.js.map +1 -0
  16. package/lib/commonjs/index.js +14 -0
  17. package/lib/commonjs/index.js.map +1 -0
  18. package/lib/commonjs/native/CameraView.js +109 -0
  19. package/lib/commonjs/native/CameraView.js.map +1 -0
  20. package/lib/commonjs/native/FrameGrabber.js +17 -0
  21. package/lib/commonjs/native/FrameGrabber.js.map +1 -0
  22. package/lib/commonjs/native/MediaEditor.js +24 -0
  23. package/lib/commonjs/native/MediaEditor.js.map +1 -0
  24. package/lib/commonjs/native/MediaLibrary.js +45 -0
  25. package/lib/commonjs/native/MediaLibrary.js.map +1 -0
  26. package/lib/commonjs/native/MediaPicker.js +17 -0
  27. package/lib/commonjs/native/MediaPicker.js.map +1 -0
  28. package/lib/commonjs/native/MediaPlayer.js +17 -0
  29. package/lib/commonjs/native/MediaPlayer.js.map +1 -0
  30. package/lib/commonjs/native/VideoPreview.js +17 -0
  31. package/lib/commonjs/native/VideoPreview.js.map +1 -0
  32. package/lib/commonjs/package.json +1 -0
  33. package/lib/commonjs/screens/CropScreen.js +1233 -0
  34. package/lib/commonjs/screens/CropScreen.js.map +1 -0
  35. package/lib/commonjs/screens/EditorScreen.js +6043 -0
  36. package/lib/commonjs/screens/EditorScreen.js.map +1 -0
  37. package/lib/commonjs/screens/ExportScreen.js +294 -0
  38. package/lib/commonjs/screens/ExportScreen.js.map +1 -0
  39. package/lib/commonjs/screens/GalleryScreen.js +510 -0
  40. package/lib/commonjs/screens/GalleryScreen.js.map +1 -0
  41. package/lib/commonjs/screens/PickScreen.js +1353 -0
  42. package/lib/commonjs/screens/PickScreen.js.map +1 -0
  43. package/lib/commonjs/types.js +2 -0
  44. package/lib/commonjs/types.js.map +1 -0
  45. package/lib/module/components/VideoEditor.js +104 -31
  46. package/lib/module/components/VideoEditor.js.map +1 -1
  47. package/lib/module/screens/CropScreen.js +26 -9
  48. package/lib/module/screens/CropScreen.js.map +1 -1
  49. package/lib/module/screens/EditorScreen.js +371 -86
  50. package/lib/module/screens/EditorScreen.js.map +1 -1
  51. package/lib/module/screens/PickScreen.js +245 -93
  52. package/lib/module/screens/PickScreen.js.map +1 -1
  53. package/lib/typescript/src/components/VideoEditor.d.ts +18 -2
  54. package/lib/typescript/src/screens/CropScreen.d.ts +3 -1
  55. package/lib/typescript/src/screens/EditorScreen.d.ts +2 -1
  56. package/lib/typescript/src/screens/PickScreen.d.ts +6 -1
  57. package/lib/typescript/src/types.d.ts +1 -0
  58. package/package.json +17 -8
  59. package/src/components/VideoEditor.tsx +82 -11
  60. package/src/screens/CropScreen.tsx +54 -33
  61. package/src/screens/EditorScreen.tsx +366 -106
  62. package/src/screens/PickScreen.tsx +231 -76
  63. package/src/types.ts +1 -0
@@ -0,0 +1,1233 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CropScreen = CropScreen;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
10
+ var _MediaEditor = require("../native/MediaEditor");
11
+ var _FrameGrabber = require("../native/FrameGrabber");
12
+ var _VideoPreview = require("../native/VideoPreview");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (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 (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
+ const {
16
+ width: SCREEN_WIDTH,
17
+ height: SCREEN_HEIGHT
18
+ } = _reactNative.Dimensions.get('window');
19
+
20
+ // --- Icons (View-based) ---
21
+ const HomeIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
22
+ style: iconStyles.homeContainer,
23
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
24
+ style: iconStyles.homeRoof
25
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
26
+ style: iconStyles.homeBase
27
+ })]
28
+ });
29
+ const UndoIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
30
+ style: iconStyles.arrowContainer,
31
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
32
+ style: iconStyles.arrowText,
33
+ children: "\u27F2"
34
+ })
35
+ });
36
+ const RedoIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
37
+ style: iconStyles.arrowContainer,
38
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
39
+ style: iconStyles.arrowText,
40
+ children: "\u27F3"
41
+ })
42
+ });
43
+ const EyeIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
44
+ style: iconStyles.eyeContainer,
45
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
46
+ style: iconStyles.eyeOuter
47
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
48
+ style: iconStyles.eyeInner
49
+ })]
50
+ });
51
+ const ShareIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
52
+ style: iconStyles.shareContainer,
53
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
54
+ style: iconStyles.shareArrow
55
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
56
+ style: iconStyles.shareBox
57
+ })]
58
+ });
59
+ const ResetIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
60
+ style: iconStyles.resetIconText,
61
+ children: "\u21BA"
62
+ });
63
+ const FlipIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
64
+ style: iconStyles.flipContainer,
65
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
66
+ style: iconStyles.flipHalf
67
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
68
+ style: [iconStyles.flipHalf, iconStyles.flipRight]
69
+ })]
70
+ });
71
+ const RotateIcon = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
72
+ style: iconStyles.rotateIconText,
73
+ children: "\u21BB"
74
+ });
75
+ const ChevronDown = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
76
+ style: {
77
+ color: '#fff',
78
+ fontSize: 10
79
+ },
80
+ children: "\u25BC"
81
+ });
82
+ const iconStyles = _reactNative.StyleSheet.create({
83
+ homeContainer: {
84
+ width: 24,
85
+ height: 24,
86
+ alignItems: 'center',
87
+ justifyContent: 'center'
88
+ },
89
+ homeRoof: {
90
+ width: 0,
91
+ height: 0,
92
+ borderLeftWidth: 10,
93
+ borderRightWidth: 10,
94
+ borderBottomWidth: 10,
95
+ borderStyle: 'solid',
96
+ backgroundColor: 'transparent',
97
+ borderLeftColor: 'transparent',
98
+ borderRightColor: 'transparent',
99
+ borderBottomColor: '#fff',
100
+ marginBottom: -2
101
+ },
102
+ homeBase: {
103
+ width: 16,
104
+ height: 10,
105
+ backgroundColor: '#fff',
106
+ borderRadius: 1
107
+ },
108
+ arrowContainer: {
109
+ width: 24,
110
+ height: 24,
111
+ alignItems: 'center',
112
+ justifyContent: 'center'
113
+ },
114
+ arrowText: {
115
+ color: '#fff',
116
+ fontSize: 20,
117
+ fontWeight: 'bold'
118
+ },
119
+ eyeContainer: {
120
+ width: 24,
121
+ height: 24,
122
+ alignItems: 'center',
123
+ justifyContent: 'center'
124
+ },
125
+ eyeOuter: {
126
+ width: 18,
127
+ height: 10,
128
+ borderRadius: 5,
129
+ borderWidth: 2,
130
+ borderColor: '#fff'
131
+ },
132
+ eyeInner: {
133
+ position: 'absolute',
134
+ width: 4,
135
+ height: 4,
136
+ borderRadius: 2,
137
+ backgroundColor: '#fff'
138
+ },
139
+ shareContainer: {
140
+ width: 24,
141
+ height: 24,
142
+ alignItems: 'center',
143
+ justifyContent: 'center'
144
+ },
145
+ shareBox: {
146
+ width: 14,
147
+ height: 10,
148
+ borderWidth: 2,
149
+ borderColor: '#fff',
150
+ borderTopWidth: 0,
151
+ borderRadius: 1
152
+ },
153
+ shareArrow: {
154
+ width: 2,
155
+ height: 14,
156
+ backgroundColor: '#fff',
157
+ position: 'absolute',
158
+ top: 2
159
+ },
160
+ resetIconText: {
161
+ color: '#fff',
162
+ fontSize: 18
163
+ },
164
+ flipContainer: {
165
+ width: 24,
166
+ height: 20,
167
+ flexDirection: 'row',
168
+ gap: 2
169
+ },
170
+ flipHalf: {
171
+ flex: 1,
172
+ backgroundColor: '#444',
173
+ borderTopLeftRadius: 4,
174
+ borderBottomLeftRadius: 4
175
+ },
176
+ flipRight: {
177
+ backgroundColor: '#fff',
178
+ borderTopLeftRadius: 0,
179
+ borderBottomLeftRadius: 0,
180
+ borderTopRightRadius: 4,
181
+ borderBottomRightRadius: 4
182
+ },
183
+ rotateIconText: {
184
+ color: '#fff',
185
+ fontSize: 20
186
+ }
187
+ });
188
+ const ratios = [{
189
+ label: 'Free',
190
+ ratio: null
191
+ }, {
192
+ label: 'Square',
193
+ ratio: 1
194
+ }, {
195
+ label: '4:5',
196
+ ratio: 4 / 5
197
+ }, {
198
+ label: '3:4',
199
+ ratio: 3 / 4
200
+ }, {
201
+ label: '9:16',
202
+ ratio: 9 / 16
203
+ }, {
204
+ label: '16:9',
205
+ ratio: 16 / 9
206
+ }, {
207
+ label: '2:3',
208
+ ratio: 2 / 3
209
+ }, {
210
+ label: '3:2',
211
+ ratio: 3 / 2
212
+ }];
213
+ function CropScreen({
214
+ item,
215
+ onBack,
216
+ onSave,
217
+ aspectRatio = 'free',
218
+ maxVideoDurationMs
219
+ }) {
220
+ const isRatioLocked = aspectRatio !== 'free';
221
+ const getInitialRatioLabel = () => {
222
+ if (!aspectRatio || aspectRatio === 'free') return 'Free';
223
+ if (aspectRatio === '1:1') return 'Square';
224
+ return aspectRatio; // '4:3', '4:5', '16:9', '9:16'
225
+ };
226
+ const [straightenAngle, setStraightenAngle] = (0, _react.useState)(0);
227
+ const [rotation, setRotation] = (0, _react.useState)(0);
228
+ const [selectedRatio, setSelectedRatio] = (0, _react.useState)(getInitialRatioLabel());
229
+ const [isFixedRatio, setIsFixedRatio] = (0, _react.useState)(isRatioLocked);
230
+ const [loading, setLoading] = (0, _react.useState)(true);
231
+ const [saving, setSaving] = (0, _react.useState)(false);
232
+ const [imageSize, setImageSize] = (0, _react.useState)({
233
+ width: 0,
234
+ height: 0
235
+ });
236
+ const [mediaLayout, setMediaLayout] = (0, _react.useState)({
237
+ x: 0,
238
+ y: 0,
239
+ width: 0,
240
+ height: 0
241
+ });
242
+ const [renderedImageSize, setRenderedImageSize] = (0, _react.useState)({
243
+ width: 0,
244
+ height: 0,
245
+ top: 0,
246
+ left: 0
247
+ });
248
+ const renderedImageSizeRef = (0, _react.useRef)(renderedImageSize);
249
+
250
+ // Crop State
251
+ const [cropBox, setCropBox] = (0, _react.useState)({
252
+ top: 50,
253
+ left: 20,
254
+ width: SCREEN_WIDTH - 40,
255
+ height: 150
256
+ });
257
+ const [resolution, setResolution] = (0, _react.useState)({
258
+ w: 0,
259
+ h: 0
260
+ });
261
+
262
+ // REF-BASED BACKUP FOR PANRESPONDERS (Prevents closure traps)
263
+ const cropBoxRef = (0, _react.useRef)(cropBox);
264
+ (0, _react.useEffect)(() => {
265
+ cropBoxRef.current = cropBox;
266
+ }, [cropBox]);
267
+ (0, _react.useEffect)(() => {
268
+ renderedImageSizeRef.current = renderedImageSize;
269
+ }, [renderedImageSize]);
270
+ (0, _react.useEffect)(() => {
271
+ if (item.type === 'image') {
272
+ _reactNative.Image.getSize(item.uri, (w, h) => {
273
+ setImageSize({
274
+ width: w,
275
+ height: h
276
+ });
277
+ });
278
+ } else {
279
+ (async () => {
280
+ try {
281
+ const frameUri = await (0, _FrameGrabber.captureFrame)(item.uri, {
282
+ timeMs: 0
283
+ });
284
+ _reactNative.Image.getSize(frameUri, (w, h) => {
285
+ setImageSize({
286
+ width: w,
287
+ height: h
288
+ });
289
+ });
290
+ } catch {
291
+ setImageSize({
292
+ width: 1080,
293
+ height: 1920
294
+ });
295
+ }
296
+ })();
297
+ }
298
+ }, [item.uri, item.type]);
299
+
300
+ // Calculate the actual area occupied by the image (contain)
301
+ (0, _react.useEffect)(() => {
302
+ if (imageSize.width && mediaLayout.width) {
303
+ const containerRatio = mediaLayout.width / mediaLayout.height;
304
+ const isRotated = rotation % 180 === 90;
305
+ const imgW = isRotated ? imageSize.height : imageSize.width;
306
+ const imgH = isRotated ? imageSize.width : imageSize.height;
307
+ const imageRatio = imgW / imgH;
308
+ let w, h;
309
+ if (imageRatio > containerRatio) {
310
+ w = mediaLayout.width;
311
+ h = w / imageRatio;
312
+ } else {
313
+ h = mediaLayout.height;
314
+ w = h * imageRatio;
315
+ }
316
+ const layoutW = isRotated ? h : w;
317
+ const layoutH = isRotated ? w : h;
318
+ const layoutL = (w - layoutW) / 2;
319
+ const layoutT = (h - layoutH) / 2;
320
+ const rendered = {
321
+ width: w,
322
+ height: h,
323
+ top: (mediaLayout.height - h) / 2,
324
+ left: (mediaLayout.width - w) / 2,
325
+ imgW: layoutW,
326
+ imgH: layoutH,
327
+ imgL: layoutL,
328
+ imgT: layoutT
329
+ };
330
+ setRenderedImageSize(rendered);
331
+ setCropBox(() => {
332
+ const ratioObj = ratios.find(r => r.label === selectedRatio);
333
+ const ratio = ratioObj ? ratioObj.ratio : null;
334
+ if (ratio) {
335
+ let newWidth = rendered.width;
336
+ let newHeight = newWidth / ratio;
337
+ if (newHeight > rendered.height) {
338
+ newHeight = rendered.height;
339
+ newWidth = newHeight * ratio;
340
+ }
341
+ if (newWidth > rendered.width) {
342
+ newWidth = rendered.width;
343
+ newHeight = newWidth / ratio;
344
+ }
345
+ return {
346
+ width: newWidth,
347
+ height: newHeight,
348
+ left: rendered.left + (rendered.width - newWidth) / 2,
349
+ top: rendered.top + (rendered.height - newHeight) / 2
350
+ };
351
+ } else {
352
+ return {
353
+ width: rendered.width,
354
+ height: rendered.height,
355
+ left: rendered.left,
356
+ top: rendered.top
357
+ };
358
+ }
359
+ });
360
+ }
361
+ }, [imageSize, mediaLayout, rotation, selectedRatio]);
362
+ const [flipX, setFlipX] = (0, _react.useState)(false);
363
+ const [flipY, setFlipY] = (0, _react.useState)(false);
364
+ const handleRatioSelect = (label, ratio) => {
365
+ setSelectedRatio(label);
366
+ };
367
+ const updateResolution = () => {
368
+ if (imageSize.width && renderedImageSize.width) {
369
+ const isRotated = rotation % 180 === 90;
370
+ const pixelWidth = isRotated ? imageSize.height : imageSize.width;
371
+ const scale = pixelWidth / renderedImageSize.width;
372
+ setResolution({
373
+ w: Math.round(cropBox.width * scale),
374
+ h: Math.round(cropBox.height * scale)
375
+ });
376
+ }
377
+ };
378
+ (0, _react.useEffect)(() => {
379
+ updateResolution();
380
+ }, [cropBox, renderedImageSize, rotation, imageSize]);
381
+ const initialCrop = (0, _react.useRef)({
382
+ top: 0,
383
+ left: 0,
384
+ width: 0,
385
+ height: 0
386
+ }).current;
387
+ const getClampedBox = (left, top, width, height) => {
388
+ const rendered = renderedImageSizeRef.current;
389
+ if (rendered.width === 0) return {
390
+ left,
391
+ top,
392
+ width,
393
+ height
394
+ };
395
+ const minSize = 60;
396
+ const imageRight = rendered.left + rendered.width;
397
+ const imageBottom = rendered.top + rendered.height;
398
+
399
+ // 1. Clamp Position to the VIRTUAL CANVAS (not just original image)
400
+ let l = Math.max(rendered.left, Math.min(left, imageRight - minSize));
401
+ let t = Math.max(rendered.top, Math.min(top, imageBottom - minSize));
402
+
403
+ // 2. Clamp Size to the VIRTUAL CANVAS
404
+ let w = Math.max(minSize, Math.min(width, imageRight - l));
405
+ let h = Math.max(minSize, Math.min(height, imageBottom - t));
406
+ if (l + w > imageRight) l = imageRight - w;
407
+ if (t + h > imageBottom) t = imageBottom - h;
408
+ return {
409
+ left: l,
410
+ top: t,
411
+ width: w,
412
+ height: h
413
+ };
414
+ };
415
+ const updateCrop = newBox => {
416
+ setCropBox(getClampedBox(newBox.left, newBox.top, newBox.width, newBox.height));
417
+ };
418
+ const panMove = (0, _react.useRef)(_reactNative.PanResponder.create({
419
+ onStartShouldSetPanResponder: () => true,
420
+ onPanResponderGrant: () => {
421
+ initialCrop.top = cropBoxRef.current.top;
422
+ initialCrop.left = cropBoxRef.current.left;
423
+ initialCrop.width = cropBoxRef.current.width;
424
+ initialCrop.height = cropBoxRef.current.height;
425
+ },
426
+ onPanResponderMove: (_, gesture) => {
427
+ const rendered = renderedImageSizeRef.current;
428
+ let l = initialCrop.left + gesture.dx;
429
+ let t = initialCrop.top + gesture.dy;
430
+ l = Math.max(rendered.left, Math.min(l, rendered.left + rendered.width - initialCrop.width));
431
+ t = Math.max(rendered.top, Math.min(t, rendered.top + rendered.height - initialCrop.height));
432
+ setCropBox(prev => ({
433
+ ...prev,
434
+ left: l,
435
+ top: t
436
+ }));
437
+ },
438
+ onPanResponderRelease: () => updateResolution()
439
+ })).current;
440
+ const createSideResponder = side => _reactNative.PanResponder.create({
441
+ onStartShouldSetPanResponder: () => true,
442
+ onPanResponderGrant: () => {
443
+ initialCrop.top = cropBoxRef.current.top;
444
+ initialCrop.left = cropBoxRef.current.left;
445
+ initialCrop.width = cropBoxRef.current.width;
446
+ initialCrop.height = cropBoxRef.current.height;
447
+ },
448
+ onPanResponderMove: (_, gesture) => {
449
+ let {
450
+ top,
451
+ left,
452
+ width,
453
+ height
454
+ } = initialCrop;
455
+ if (side === 'left') {
456
+ left += gesture.dx;
457
+ width -= gesture.dx;
458
+ } else if (side === 'right') {
459
+ width += gesture.dx;
460
+ }
461
+ updateCrop({
462
+ top,
463
+ left,
464
+ width,
465
+ height
466
+ });
467
+ },
468
+ onPanResponderRelease: () => updateResolution()
469
+ });
470
+ const panLeft = (0, _react.useRef)(createSideResponder('left')).current;
471
+ const panRight = (0, _react.useRef)(createSideResponder('right')).current;
472
+ const createCornerResponder = corner => _reactNative.PanResponder.create({
473
+ onStartShouldSetPanResponder: () => true,
474
+ onPanResponderGrant: () => {
475
+ initialCrop.top = cropBoxRef.current.top;
476
+ initialCrop.left = cropBoxRef.current.left;
477
+ initialCrop.width = cropBoxRef.current.width;
478
+ initialCrop.height = cropBoxRef.current.height;
479
+ },
480
+ onPanResponderMove: (_, gesture) => {
481
+ let {
482
+ top: t,
483
+ left: l,
484
+ width: w,
485
+ height: h
486
+ } = initialCrop;
487
+ const ratio = ratios.find(r => r.label === selectedRatio)?.ratio;
488
+ if (corner === 'TL') {
489
+ t += gesture.dy;
490
+ l += gesture.dx;
491
+ w -= gesture.dx;
492
+ h -= gesture.dy;
493
+ } else if (corner === 'TR') {
494
+ t += gesture.dy;
495
+ w += gesture.dx;
496
+ h -= gesture.dy;
497
+ } else if (corner === 'BL') {
498
+ l += gesture.dx;
499
+ w -= gesture.dx;
500
+ h += gesture.dy;
501
+ } else if (corner === 'BR') {
502
+ w += gesture.dx;
503
+ h += gesture.dy;
504
+ }
505
+ if (ratio && isFixedRatio) {
506
+ if (corner === 'BR' || corner === 'BL') h = w / ratio;else w = h * ratio;
507
+ }
508
+ updateCrop({
509
+ top: t,
510
+ left: l,
511
+ width: w,
512
+ height: h
513
+ });
514
+ },
515
+ onPanResponderRelease: () => updateResolution()
516
+ });
517
+ const panTL = (0, _react.useRef)(createCornerResponder('TL')).current;
518
+ const panTR = (0, _react.useRef)(createCornerResponder('TR')).current;
519
+ const panBL = (0, _react.useRef)(createCornerResponder('BL')).current;
520
+ const panBR = (0, _react.useRef)(createCornerResponder('BR')).current;
521
+ const handleSave = async () => {
522
+ try {
523
+ setSaving(true);
524
+ if (!imageSize.width || !renderedImageSize.width) return;
525
+ const isRotated = rotation % 180 === 90;
526
+ const pixelWidth = isRotated ? imageSize.height : imageSize.width;
527
+ const scale = pixelWidth / renderedImageSize.width;
528
+ const finalCrop = {
529
+ x: Math.round((cropBox.left - renderedImageSize.left) * scale),
530
+ y: Math.round((cropBox.top - renderedImageSize.top) * scale),
531
+ width: Math.round(cropBox.width * scale),
532
+ height: Math.round(cropBox.height * scale)
533
+ };
534
+ const options = {
535
+ rotateDegrees: straightenAngle + rotation,
536
+ flipX,
537
+ flipY,
538
+ crop: finalCrop,
539
+ brightness: 0,
540
+ contrast: 1,
541
+ saturation: 1,
542
+ grayscale: false
543
+ };
544
+ const outUri = item.type === 'image' ? await (0, _MediaEditor.editImage)(item.uri, options) : await (0, _MediaEditor.trimVideo)(item.uri, {
545
+ startMs: 0,
546
+ endMs: maxVideoDurationMs ? Math.min(item.durationMs || 10000, maxVideoDurationMs) : item.durationMs || 10000,
547
+ ...options
548
+ });
549
+ const clampedDuration = item.type === 'video' && maxVideoDurationMs ? Math.min(item.durationMs || 10000, maxVideoDurationMs) : item.durationMs;
550
+ onSave(outUri, item.type === 'image' ? outUri : undefined, clampedDuration);
551
+ } catch (err) {
552
+ _reactNative.Alert.alert('Apply failed', err?.message ?? 'Could not process crop.');
553
+ } finally {
554
+ setSaving(false);
555
+ }
556
+ };
557
+ const handleReset = () => {
558
+ setStraightenAngle(0);
559
+ setRotation(0);
560
+ setFlipX(false);
561
+ setFlipY(false);
562
+ setSelectedRatio(getInitialRatioLabel());
563
+ setIsFixedRatio(isRatioLocked);
564
+ };
565
+ const sliderPan = (0, _react.useRef)(_reactNative.PanResponder.create({
566
+ onStartShouldSetPanResponder: () => true,
567
+ onPanResponderMove: (_, gesture) => {
568
+ const delta = gesture.dx * 0.1;
569
+ setStraightenAngle(prev => Math.max(-45, Math.min(45, prev + delta)));
570
+ }
571
+ })).current;
572
+ const renderDial = () => {
573
+ const marks = [];
574
+ for (let i = -30; i <= 30; i++) {
575
+ const isMajor = i % 5 === 0;
576
+ const isCenter = i === 0;
577
+ marks.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
578
+ style: [styles.dialMark, isMajor && styles.dialMarkLong, isCenter && styles.dialMarkCenter, Math.abs(i - straightenAngle / 2) < 0.5 && styles.dialMarkActive]
579
+ }, i));
580
+ }
581
+ return marks;
582
+ };
583
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
584
+ style: styles.container,
585
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSafeAreaContext.SafeAreaView, {
586
+ style: styles.safeArea,
587
+ edges: ['left', 'right', 'bottom'],
588
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
589
+ style: styles.topBar,
590
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
591
+ onPress: onBack,
592
+ style: styles.topBtn,
593
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
594
+ style: {
595
+ color: '#fff',
596
+ fontSize: 16
597
+ },
598
+ children: "Cancel"
599
+ })
600
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
601
+ style: {
602
+ width: 60
603
+ }
604
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
605
+ style: {
606
+ width: 60
607
+ }
608
+ })]
609
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
610
+ style: styles.previewContainer,
611
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
612
+ style: styles.mediaContainer,
613
+ onLayout: e => setMediaLayout(e.nativeEvent.layout),
614
+ children: [renderedImageSize.width > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
615
+ style: {
616
+ position: 'absolute',
617
+ top: renderedImageSize.top,
618
+ left: renderedImageSize.left,
619
+ width: renderedImageSize.width,
620
+ height: renderedImageSize.height,
621
+ backgroundColor: '#000'
622
+ },
623
+ children: item.type === 'video' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_VideoPreview.VideoPreview, {
624
+ uri: item.uri,
625
+ paused: false,
626
+ muted: true,
627
+ style: [styles.media, {
628
+ position: 'absolute',
629
+ top: renderedImageSize.imgT,
630
+ left: renderedImageSize.imgL,
631
+ width: renderedImageSize.imgW,
632
+ height: renderedImageSize.imgH,
633
+ transform: [{
634
+ rotate: `${rotation}deg`
635
+ }, {
636
+ rotate: `${straightenAngle}deg`
637
+ }, {
638
+ scaleX: flipX ? -1 : 1
639
+ }, {
640
+ scaleY: flipY ? -1 : 1
641
+ }]
642
+ }],
643
+ resizeMode: "contain"
644
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
645
+ source: {
646
+ uri: item.uri
647
+ },
648
+ style: [styles.media, {
649
+ position: 'absolute',
650
+ top: renderedImageSize.imgT,
651
+ left: renderedImageSize.imgL,
652
+ width: renderedImageSize.imgW,
653
+ height: renderedImageSize.imgH,
654
+ transform: [{
655
+ rotate: `${rotation}deg`
656
+ }, {
657
+ rotate: `${straightenAngle}deg`
658
+ }, {
659
+ scaleX: flipX ? -1 : 1
660
+ }, {
661
+ scaleY: flipY ? -1 : 1
662
+ }]
663
+ }],
664
+ resizeMode: "contain"
665
+ })
666
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
667
+ style: [styles.shading, {
668
+ top: renderedImageSize.top,
669
+ left: renderedImageSize.left,
670
+ width: renderedImageSize.width,
671
+ height: cropBox.top - renderedImageSize.top
672
+ }],
673
+ pointerEvents: "none"
674
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
675
+ style: [styles.shading, {
676
+ top: cropBox.top + cropBox.height,
677
+ left: renderedImageSize.left,
678
+ width: renderedImageSize.width,
679
+ height: renderedImageSize.top + renderedImageSize.height - (cropBox.top + cropBox.height)
680
+ }],
681
+ pointerEvents: "none"
682
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
683
+ style: [styles.shading, {
684
+ top: cropBox.top,
685
+ left: renderedImageSize.left,
686
+ width: cropBox.left - renderedImageSize.left,
687
+ height: cropBox.height
688
+ }],
689
+ pointerEvents: "none"
690
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
691
+ style: [styles.shading, {
692
+ top: cropBox.top,
693
+ left: cropBox.left + cropBox.width,
694
+ width: renderedImageSize.left + renderedImageSize.width - (cropBox.left + cropBox.width),
695
+ height: cropBox.height
696
+ }],
697
+ pointerEvents: "none"
698
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
699
+ style: [styles.cropBox, {
700
+ top: cropBox.top,
701
+ left: cropBox.left,
702
+ width: cropBox.width,
703
+ height: cropBox.height,
704
+ position: 'absolute'
705
+ }],
706
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
707
+ style: [_reactNative.StyleSheet.absoluteFill, {
708
+ backgroundColor: 'transparent'
709
+ }],
710
+ ...panMove.panHandlers
711
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
712
+ style: [styles.gridV1, {
713
+ pointerEvents: 'none'
714
+ }]
715
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
716
+ style: [styles.gridV2, {
717
+ pointerEvents: 'none'
718
+ }]
719
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
720
+ style: [styles.gridH1, {
721
+ pointerEvents: 'none'
722
+ }]
723
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
724
+ style: [styles.gridH2, {
725
+ pointerEvents: 'none'
726
+ }]
727
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
728
+ style: [styles.corner, styles.cornerTL, {
729
+ zIndex: 1000
730
+ }],
731
+ ...panTL.panHandlers,
732
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
733
+ style: [styles.cornerVisual, {
734
+ borderTopWidth: 4,
735
+ borderLeftWidth: 4
736
+ }]
737
+ })
738
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
739
+ style: [styles.corner, styles.cornerTR, {
740
+ zIndex: 1000
741
+ }],
742
+ ...panTR.panHandlers,
743
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
744
+ style: [styles.cornerVisual, {
745
+ borderTopWidth: 4,
746
+ borderRightWidth: 4
747
+ }]
748
+ })
749
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
750
+ style: [styles.corner, styles.cornerBL, {
751
+ zIndex: 1000
752
+ }],
753
+ ...panBL.panHandlers,
754
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
755
+ style: [styles.cornerVisual, {
756
+ borderBottomWidth: 4,
757
+ borderLeftWidth: 4
758
+ }]
759
+ })
760
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
761
+ style: [styles.corner, styles.cornerBR, {
762
+ zIndex: 1000
763
+ }],
764
+ ...panBR.panHandlers,
765
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
766
+ style: [styles.cornerVisual, {
767
+ borderBottomWidth: 4,
768
+ borderRightWidth: 4
769
+ }]
770
+ })
771
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
772
+ style: [styles.sideHandle, styles.sideHandleLeft, {
773
+ zIndex: 900
774
+ }],
775
+ ...panLeft.panHandlers
776
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
777
+ style: [styles.sideHandle, styles.sideHandleRight, {
778
+ zIndex: 900
779
+ }],
780
+ ...panRight.panHandlers
781
+ })]
782
+ })]
783
+ })
784
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
785
+ style: styles.bottomPanel,
786
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
787
+ style: styles.panelHeader,
788
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
789
+ style: styles.resetBtn,
790
+ onPress: handleReset,
791
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ResetIcon, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
792
+ style: styles.resetText,
793
+ children: "Reset"
794
+ })]
795
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
796
+ style: styles.panelTitleContainer,
797
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
798
+ style: styles.panelTitle,
799
+ children: "Aspect Ratio"
800
+ })
801
+ }), !isRatioLocked ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
802
+ style: [styles.fixedRatioBtn, isFixedRatio && styles.fixedRatioBtnActive],
803
+ onPress: () => setIsFixedRatio(!isFixedRatio),
804
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
805
+ style: styles.fixedRatioText,
806
+ children: isFixedRatio ? 'Locked' : 'Unlocked'
807
+ })
808
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
809
+ style: [styles.fixedRatioBtn, styles.fixedRatioBtnActive, {
810
+ opacity: 0.7
811
+ }],
812
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
813
+ style: styles.fixedRatioText,
814
+ children: "Locked"
815
+ })
816
+ })]
817
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
818
+ style: styles.straightenContainer,
819
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
820
+ style: styles.sliderWrapper,
821
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
822
+ style: [styles.flipBtn, (flipX || flipY) && {
823
+ backgroundColor: '#333',
824
+ borderRadius: 8
825
+ }],
826
+ onPress: () => setFlipX(!flipX),
827
+ onLongPress: () => setFlipY(!flipY),
828
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(FlipIcon, {})
829
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
830
+ style: styles.dialContainer,
831
+ ...sliderPan.panHandlers,
832
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
833
+ style: styles.dialCenterLine
834
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
835
+ style: styles.dialMarksWrapper,
836
+ children: renderDial()
837
+ })]
838
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
839
+ style: styles.rotateBtn,
840
+ onPress: () => setRotation(r => (r + 90) % 360),
841
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(RotateIcon, {})
842
+ })]
843
+ })
844
+ }), !isRatioLocked && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
845
+ horizontal: true,
846
+ showsHorizontalScrollIndicator: false,
847
+ style: styles.ratioScroll,
848
+ contentContainerStyle: styles.ratioContent,
849
+ children: ratios.map(r => {
850
+ const isSelected = selectedRatio === r.label;
851
+ const boxRatio = r.ratio || 1;
852
+ // Limit box size for icon
853
+ const boxStyle = {
854
+ width: boxRatio > 1 ? 24 : 24 * boxRatio,
855
+ height: boxRatio > 1 ? 24 / boxRatio : 24,
856
+ borderWidth: 1.5,
857
+ borderColor: isSelected ? '#4A8CFF' : '#666',
858
+ borderRadius: 2
859
+ };
860
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
861
+ style: styles.ratioItem,
862
+ onPress: () => handleRatioSelect(r.label, r.ratio),
863
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
864
+ style: [styles.ratioIconBox, isSelected && styles.ratioIconBoxActive],
865
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
866
+ style: boxStyle
867
+ })
868
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
869
+ style: [styles.ratioLabel, isSelected && styles.ratioLabelActive],
870
+ children: r.label
871
+ })]
872
+ }, r.label);
873
+ })
874
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
875
+ style: styles.footerStatus,
876
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
877
+ style: styles.footerLeft,
878
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
879
+ style: styles.resolutionText,
880
+ children: [resolution.w, " \xD7 ", resolution.h, " px"]
881
+ })
882
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
883
+ onPress: handleSave,
884
+ style: styles.primarySaveBtn,
885
+ disabled: saving,
886
+ children: saving ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
887
+ style: styles.saveText,
888
+ children: "Applying..."
889
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
890
+ style: styles.saveText,
891
+ children: "Apply Crop"
892
+ })
893
+ })]
894
+ })]
895
+ })]
896
+ })
897
+ });
898
+ }
899
+ const styles = _reactNative.StyleSheet.create({
900
+ container: {
901
+ flex: 1,
902
+ backgroundColor: '#000'
903
+ },
904
+ safeArea: {
905
+ flex: 1
906
+ },
907
+ quickActions: {
908
+ flexDirection: 'row',
909
+ gap: 12
910
+ },
911
+ quickActionBtn: {
912
+ paddingHorizontal: 12,
913
+ paddingVertical: 6,
914
+ borderRadius: 15,
915
+ backgroundColor: '#1a1a1a',
916
+ borderWidth: 1,
917
+ borderColor: '#333'
918
+ },
919
+ quickActionBtnActive: {
920
+ backgroundColor: '#fff',
921
+ borderColor: '#fff'
922
+ },
923
+ quickActionText: {
924
+ color: '#999',
925
+ fontSize: 10,
926
+ fontWeight: '800'
927
+ },
928
+ quickActionTextActive: {
929
+ color: '#000'
930
+ },
931
+ topBar: {
932
+ flexDirection: 'row',
933
+ alignItems: 'center',
934
+ justifyContent: 'space-between',
935
+ paddingHorizontal: 16,
936
+ height: 56
937
+ },
938
+ topBtn: {
939
+ padding: 8
940
+ },
941
+ saveText: {
942
+ color: '#fff',
943
+ fontSize: 16,
944
+ fontWeight: '700'
945
+ },
946
+ primarySaveBtn: {
947
+ backgroundColor: '#4A8CFF',
948
+ paddingHorizontal: 24,
949
+ paddingVertical: 12,
950
+ borderRadius: 12
951
+ },
952
+ previewContainer: {
953
+ flex: 1,
954
+ backgroundColor: '#000',
955
+ justifyContent: 'center',
956
+ alignItems: 'center'
957
+ },
958
+ mediaContainer: {
959
+ flex: 1,
960
+ // Full flexible space
961
+ width: SCREEN_WIDTH,
962
+ justifyContent: 'center',
963
+ alignItems: 'center'
964
+ },
965
+ media: {
966
+ width: '100%',
967
+ height: '100%'
968
+ },
969
+ cropBox: {
970
+ position: 'absolute',
971
+ borderWidth: 1,
972
+ borderColor: '#4A8CFF',
973
+ backgroundColor: 'transparent'
974
+ },
975
+ gridV1: {
976
+ position: 'absolute',
977
+ left: '33.3%',
978
+ top: 0,
979
+ bottom: 0,
980
+ width: 0.5,
981
+ backgroundColor: 'rgba(255,255,255,0.5)'
982
+ },
983
+ gridV2: {
984
+ position: 'absolute',
985
+ left: '66.6%',
986
+ top: 0,
987
+ bottom: 0,
988
+ width: 0.5,
989
+ backgroundColor: 'rgba(255,255,255,0.5)'
990
+ },
991
+ gridH1: {
992
+ position: 'absolute',
993
+ top: '33.3%',
994
+ left: 0,
995
+ right: 0,
996
+ height: 0.5,
997
+ backgroundColor: 'rgba(255,255,255,0.5)'
998
+ },
999
+ gridH2: {
1000
+ position: 'absolute',
1001
+ top: '66.6%',
1002
+ left: 0,
1003
+ right: 0,
1004
+ height: 0.5,
1005
+ backgroundColor: 'rgba(255,255,255,0.5)'
1006
+ },
1007
+ corner: {
1008
+ position: 'absolute',
1009
+ width: 40,
1010
+ height: 40,
1011
+ justifyContent: 'center',
1012
+ alignItems: 'center',
1013
+ zIndex: 100
1014
+ },
1015
+ shading: {
1016
+ position: 'absolute',
1017
+ backgroundColor: 'rgba(0,0,0,0.6)'
1018
+ },
1019
+ cornerVisual: {
1020
+ width: 20,
1021
+ height: 20,
1022
+ borderColor: '#4A8CFF'
1023
+ },
1024
+ cornerTL: {
1025
+ top: -10,
1026
+ left: -10
1027
+ },
1028
+ cornerTR: {
1029
+ top: -10,
1030
+ right: -10
1031
+ },
1032
+ cornerBL: {
1033
+ bottom: -10,
1034
+ left: -10
1035
+ },
1036
+ cornerBR: {
1037
+ bottom: -10,
1038
+ right: -10
1039
+ },
1040
+ sideHandle: {
1041
+ position: 'absolute',
1042
+ width: 10,
1043
+ height: 30,
1044
+ backgroundColor: '#4A8CFF',
1045
+ borderRadius: 2,
1046
+ zIndex: 90
1047
+ },
1048
+ sideHandleLeft: {
1049
+ left: -2,
1050
+ top: '50%',
1051
+ marginTop: -10
1052
+ },
1053
+ sideHandleRight: {
1054
+ right: -2,
1055
+ top: '50%',
1056
+ marginTop: -10
1057
+ },
1058
+ bottomPanel: {
1059
+ backgroundColor: '#111',
1060
+ borderTopLeftRadius: 24,
1061
+ borderTopRightRadius: 24,
1062
+ paddingBottom: _reactNative.Platform.OS === 'ios' ? 20 : 10
1063
+ },
1064
+ panelHeader: {
1065
+ flexDirection: 'row',
1066
+ alignItems: 'center',
1067
+ justifyContent: 'space-between',
1068
+ paddingHorizontal: 20,
1069
+ paddingVertical: 12
1070
+ },
1071
+ resetBtn: {
1072
+ flexDirection: 'row',
1073
+ alignItems: 'center',
1074
+ gap: 6
1075
+ },
1076
+ resetText: {
1077
+ color: '#fff',
1078
+ fontSize: 14,
1079
+ fontWeight: '500'
1080
+ },
1081
+ panelTitleContainer: {
1082
+ flexDirection: 'row',
1083
+ alignItems: 'center',
1084
+ gap: 4
1085
+ },
1086
+ panelTitle: {
1087
+ color: '#fff',
1088
+ fontSize: 18,
1089
+ fontWeight: '600'
1090
+ },
1091
+ straightenContainer: {
1092
+ alignItems: 'center',
1093
+ paddingVertical: 10
1094
+ },
1095
+ straightenText: {
1096
+ color: '#999',
1097
+ fontSize: 12,
1098
+ marginBottom: 10
1099
+ },
1100
+ sliderWrapper: {
1101
+ flexDirection: 'row',
1102
+ alignItems: 'center',
1103
+ width: '100%',
1104
+ paddingHorizontal: 20,
1105
+ gap: 12
1106
+ },
1107
+ flipBtn: {
1108
+ padding: 8
1109
+ },
1110
+ rotateBtn: {
1111
+ padding: 8
1112
+ },
1113
+ dialContainer: {
1114
+ flex: 1,
1115
+ height: 40,
1116
+ backgroundColor: '#222',
1117
+ borderRadius: 12,
1118
+ overflow: 'hidden',
1119
+ justifyContent: 'center',
1120
+ alignItems: 'center'
1121
+ },
1122
+ dialCenterLine: {
1123
+ position: 'absolute',
1124
+ top: 5,
1125
+ bottom: 5,
1126
+ width: 2,
1127
+ backgroundColor: '#fff',
1128
+ zIndex: 10
1129
+ },
1130
+ dialMarksWrapper: {
1131
+ flexDirection: 'row',
1132
+ alignItems: 'center',
1133
+ gap: 8
1134
+ },
1135
+ dialMark: {
1136
+ width: 1,
1137
+ height: 8,
1138
+ backgroundColor: '#444'
1139
+ },
1140
+ dialMarkLong: {
1141
+ height: 14,
1142
+ backgroundColor: '#666'
1143
+ },
1144
+ dialMarkCenter: {
1145
+ backgroundColor: '#fff',
1146
+ height: 18,
1147
+ width: 2
1148
+ },
1149
+ dialMarkActive: {
1150
+ backgroundColor: '#fff'
1151
+ },
1152
+ ratioScroll: {
1153
+ marginTop: 20
1154
+ },
1155
+ ratioContent: {
1156
+ paddingHorizontal: 16,
1157
+ gap: 20
1158
+ },
1159
+ ratioItem: {
1160
+ alignItems: 'center',
1161
+ gap: 6,
1162
+ width: 50
1163
+ },
1164
+ ratioIconBox: {
1165
+ width: 40,
1166
+ height: 40,
1167
+ justifyContent: 'center',
1168
+ alignItems: 'center',
1169
+ borderRadius: 8,
1170
+ backgroundColor: 'transparent'
1171
+ },
1172
+ ratioIconBoxActive: {
1173
+ backgroundColor: '#333'
1174
+ },
1175
+ ratioIcon: {
1176
+ fontSize: 20,
1177
+ color: '#fff'
1178
+ },
1179
+ ratioLabel: {
1180
+ color: '#666',
1181
+ fontSize: 12
1182
+ },
1183
+ ratioLabelActive: {
1184
+ color: '#fff',
1185
+ fontWeight: '600'
1186
+ },
1187
+ footerStatus: {
1188
+ flexDirection: 'row',
1189
+ alignItems: 'center',
1190
+ justifyContent: 'space-between',
1191
+ paddingHorizontal: 20,
1192
+ paddingVertical: 16,
1193
+ borderTopWidth: 0.5,
1194
+ borderTopColor: '#222',
1195
+ marginTop: 10
1196
+ },
1197
+ footerLeft: {
1198
+ flexDirection: 'row',
1199
+ alignItems: 'center',
1200
+ gap: 8
1201
+ },
1202
+ footerIcon: {
1203
+ fontSize: 18
1204
+ },
1205
+ footerText: {
1206
+ color: '#fff',
1207
+ fontSize: 14,
1208
+ fontWeight: '500'
1209
+ },
1210
+ footerCenter: {
1211
+ flex: 1,
1212
+ alignItems: 'center'
1213
+ },
1214
+ resolutionText: {
1215
+ color: '#999',
1216
+ fontSize: 12
1217
+ },
1218
+ fixedRatioBtn: {
1219
+ backgroundColor: '#222',
1220
+ paddingHorizontal: 16,
1221
+ paddingVertical: 8,
1222
+ borderRadius: 20
1223
+ },
1224
+ fixedRatioBtnActive: {
1225
+ backgroundColor: '#333'
1226
+ },
1227
+ fixedRatioText: {
1228
+ color: '#fff',
1229
+ fontSize: 13,
1230
+ fontWeight: '500'
1231
+ }
1232
+ });
1233
+ //# sourceMappingURL=CropScreen.js.map