@technotoil/image-video-editor 0.1.0 → 0.1.1

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