@onekeyfe/react-native-image-crop-picker 3.0.147 → 3.0.148

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 (34) hide show
  1. package/README.md +48 -5
  2. package/ReactNativeImageCropPicker.podspec +5 -3
  3. package/android/src/main/AndroidManifest.xml +2 -2
  4. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerImageProcessor.kt +8 -18
  5. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerSession.kt +21 -46
  6. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperActivity.kt +636 -0
  7. package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperTheme.kt +188 -0
  8. package/android/src/main/res/drawable/image_crop_picker_rotate.xml +11 -0
  9. package/ios/ImageCropPickerImageProcessor.swift +8 -4
  10. package/ios/ImageCropPickerSession.swift +43 -46
  11. package/ios/ImageCropperTheme.swift +155 -0
  12. package/ios/ImageCropperViewController.swift +462 -0
  13. package/ios/TOCropViewController/Views/TOCropOverlayView.h +9 -0
  14. package/ios/TOCropViewController/Views/TOCropOverlayView.m +47 -3
  15. package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts +17 -11
  16. package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts.map +1 -1
  17. package/nitrogen/generated/android/c++/JHybridReactNativeImageCropPickerSpec.cpp +8 -0
  18. package/nitrogen/generated/android/c++/JImageCropPickerOptions.hpp +9 -45
  19. package/nitrogen/generated/android/c++/JImageCropperAppearance.hpp +108 -0
  20. package/nitrogen/generated/android/c++/JImageCropperColorScheme.hpp +58 -0
  21. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerOptions.kt +5 -55
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperAppearance.kt +111 -0
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperColorScheme.kt +23 -0
  24. package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Bridge.hpp +36 -0
  25. package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Umbrella.hpp +6 -0
  26. package/nitrogen/generated/ios/c++/HybridReactNativeImageCropPickerSpecSwift.hpp +6 -0
  27. package/nitrogen/generated/ios/swift/ImageCropPickerOptions.swift +6 -193
  28. package/nitrogen/generated/ios/swift/ImageCropperAppearance.swift +251 -0
  29. package/nitrogen/generated/ios/swift/ImageCropperColorScheme.swift +40 -0
  30. package/nitrogen/generated/shared/c++/ImageCropPickerOptions.hpp +8 -46
  31. package/nitrogen/generated/shared/c++/ImageCropperAppearance.hpp +134 -0
  32. package/nitrogen/generated/shared/c++/ImageCropperColorScheme.hpp +76 -0
  33. package/package.json +1 -1
  34. package/src/ReactNativeImageCropPicker.nitro.ts +29 -13
@@ -0,0 +1,462 @@
1
+ import UIKit
2
+
3
+ protocol ImageCropperViewControllerDelegate: AnyObject {
4
+ // `cropFrame` is in the coordinate space of `image`, rotated by `angle`.
5
+ func imageCropperViewController(
6
+ _ controller: ImageCropperViewController,
7
+ didCropWithFrame cropFrame: CGRect,
8
+ angle: Int
9
+ )
10
+ func imageCropperViewControllerDidCancel(_ controller: ImageCropperViewController)
11
+ }
12
+
13
+ // Full-screen cropper laid out like a OneKey page: a header with the title and
14
+ // a rotate button, the crop area, and a Cancel / Confirm footer. Android's
15
+ // ImageCropperActivity draws the same screen with the same metrics.
16
+ final class ImageCropperViewController: UIViewController {
17
+ // The area outside the crop box shows the page background at this opacity.
18
+ static let dimmedAlpha: CGFloat = 0.7
19
+
20
+ weak var delegate: ImageCropperViewControllerDelegate?
21
+ let image: UIImage
22
+
23
+ private let config: ImageCropPickerConfig
24
+ private let theme: ImageCropperTheme
25
+ private let cropView: TOCropView
26
+ private let titleLabel = UILabel()
27
+ private let rotateButton: ImageCropperIconButton
28
+ private let cancelButton: ImageCropperButton
29
+ private let confirmButton: ImageCropperButton
30
+ private var didPerformInitialSetup = false
31
+ private var isRotating = false
32
+
33
+ // Set while the session crops and saves the image.
34
+ var isProcessing = false {
35
+ didSet { updateControls() }
36
+ }
37
+
38
+ init(image: UIImage, config: ImageCropPickerConfig, theme: ImageCropperTheme) {
39
+ self.image = image
40
+ self.config = config
41
+ self.theme = theme
42
+ let cropView = TOCropView(
43
+ croppingStyle: config.cropperCircleOverlay ? .circular : .default,
44
+ image: image
45
+ )
46
+ self.cropView = cropView
47
+
48
+ // TOCropViewController's own translations, for callers that pass no text.
49
+ let strings = TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(cropView) ?? .main
50
+ func localized(_ key: String) -> String {
51
+ return strings.localizedString(forKey: key, value: key, table: "TOCropViewControllerLocalizable")
52
+ }
53
+ // Large Button: 12pt + 1pt border vertically, 20pt + 1pt horizontally.
54
+ let buttonPadding = theme.metric(20) + 1
55
+ let spinnerSpacing = theme.metric(8)
56
+ cancelButton = ImageCropperButton(
57
+ title: Self.nonEmpty(config.cropperCancelText) ?? localized("Cancel"),
58
+ horizontalPadding: buttonPadding,
59
+ spinnerSpacing: spinnerSpacing,
60
+ font: theme.buttonFont,
61
+ textColor: theme.cancelButtonTextColor,
62
+ backgroundColor: theme.cancelButtonColor,
63
+ pressedBackgroundColor: theme.cancelButtonPressedColor
64
+ )
65
+ confirmButton = ImageCropperButton(
66
+ title: Self.nonEmpty(config.cropperChooseText) ?? localized("Done"),
67
+ horizontalPadding: buttonPadding,
68
+ spinnerSpacing: spinnerSpacing,
69
+ font: theme.buttonFont,
70
+ textColor: theme.confirmButtonTextColor,
71
+ backgroundColor: theme.confirmButtonColor,
72
+ pressedBackgroundColor: theme.confirmButtonPressedColor
73
+ )
74
+ rotateButton = ImageCropperIconButton(
75
+ image: ImageCropperIcons.rotateCounterclockwise(size: theme.metric(24)),
76
+ tintColor: theme.iconColor,
77
+ pressedBackgroundColor: theme.cancelButtonColor
78
+ )
79
+
80
+ super.init(nibName: nil, bundle: nil)
81
+ modalPresentationStyle = .fullScreen
82
+ modalTransitionStyle = .coverVertical
83
+ overrideUserInterfaceStyle = theme.isDark ? .dark : .light
84
+ }
85
+
86
+ @available(*, unavailable)
87
+ required init?(coder: NSCoder) {
88
+ fatalError("init(coder:) has not been implemented")
89
+ }
90
+
91
+ override var preferredStatusBarStyle: UIStatusBarStyle {
92
+ return theme.isDark ? .lightContent : .darkContent
93
+ }
94
+
95
+ override func viewDidLoad() {
96
+ super.viewDidLoad()
97
+ view.backgroundColor = theme.backgroundColor
98
+
99
+ configureCropView()
100
+ view.addSubview(cropView)
101
+
102
+ titleLabel.text = config.cropperToolbarTitle
103
+ titleLabel.font = theme.titleFont
104
+ titleLabel.textColor = theme.titleColor
105
+ titleLabel.textAlignment = .center
106
+ titleLabel.lineBreakMode = .byTruncatingTail
107
+ titleLabel.accessibilityTraits = .header
108
+ view.addSubview(titleLabel)
109
+
110
+ rotateButton.isHidden = config.cropperRotateButtonsHidden
111
+ rotateButton.accessibilityLabel = "Rotate"
112
+ rotateButton.addTarget(self, action: #selector(rotateTapped), for: .touchUpInside)
113
+ view.addSubview(rotateButton)
114
+
115
+ cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
116
+ confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
117
+ view.addSubview(cancelButton)
118
+ view.addSubview(confirmButton)
119
+ }
120
+
121
+ private func configureCropView() {
122
+ cropView.backgroundColor = theme.backgroundColor
123
+ cropView.overlayView.backgroundColor = theme.backgroundColor.withAlphaComponent(Self.dimmedAlpha)
124
+ // The built-in dark blur ignores the theme, so rely on the dimmed overlay alone.
125
+ cropView.translucencyAlwaysHidden = true
126
+ cropView.cropViewPadding = theme.metric(20)
127
+
128
+ let isCircular = config.cropperCircleOverlay
129
+ let isFreeStyle = config.freeStyleCropEnabled && !isCircular
130
+ if !isCircular, let targetSize = config.targetSize {
131
+ // Applied by performInitialSetup, which sizes the first crop box from it.
132
+ cropView.aspectRatio = targetSize
133
+ }
134
+ cropView.aspectRatioLockEnabled = !isFreeStyle
135
+ cropView.resetAspectRatioEnabled = isFreeStyle
136
+ cropView.cropBoxResizeEnabled = isFreeStyle
137
+
138
+ if let overlay = cropView.gridOverlayView {
139
+ overlay.frameColor = theme.titleColor
140
+ overlay.gridColor = UIColor.white.withAlphaComponent(0.8)
141
+ overlay.cornerHandlesHidden = !isFreeStyle
142
+ if !config.showCropGuidelines {
143
+ overlay.displayHorizontalGridLines = false
144
+ overlay.displayVerticalGridLines = false
145
+ }
146
+ }
147
+ }
148
+
149
+ override func viewDidLayoutSubviews() {
150
+ super.viewDidLayoutSubviews()
151
+ let cropFrame = layoutChrome(in: view.bounds.size)
152
+ guard cropFrame.width > 0, cropFrame.height > 0 else {
153
+ return
154
+ }
155
+ cropView.frame = cropFrame
156
+ cropView.moveCroppedContentToCenter(animated: false)
157
+ if !didPerformInitialSetup {
158
+ didPerformInitialSetup = true
159
+ cropView.performInitialSetup()
160
+ }
161
+ }
162
+
163
+ override func viewWillTransition(
164
+ to size: CGSize,
165
+ with coordinator: UIViewControllerTransitionCoordinator
166
+ ) {
167
+ super.viewWillTransition(to: size, with: coordinator)
168
+ guard didPerformInitialSetup, size != view.bounds.size else {
169
+ return
170
+ }
171
+ // Same sequence as TOCropViewController, so the crop box keeps its content.
172
+ cropView.prepareforRotation()
173
+ cropView.simpleRenderMode = true
174
+ cropView.internalLayoutDisabled = true
175
+ coordinator.animate(alongsideTransition: { [self] _ in
176
+ cropView.frame = layoutChrome(in: size)
177
+ cropView.performRelayoutForRotation()
178
+ }, completion: { [self] _ in
179
+ cropView.setSimpleRenderMode(false, animated: true)
180
+ cropView.internalLayoutDisabled = false
181
+ })
182
+ }
183
+
184
+ // Lays out the header and footer, and returns the frame left for the crop view.
185
+ @discardableResult
186
+ private func layoutChrome(in size: CGSize) -> CGRect {
187
+ let insets = view.safeAreaInsets
188
+ let spacing = theme.metric(20)
189
+ let headerHeight = theme.metric(56)
190
+ let iconButtonSize = theme.metric(40)
191
+ let buttonHeight = theme.metric(50)
192
+ let buttonGap = theme.metric(10)
193
+ // Same as OneKey's page footer: 20pt of padding, plus the home indicator
194
+ // inset less 10pt.
195
+ let bottomInset = insets.bottom > 10 ? insets.bottom - 10 : insets.bottom
196
+
197
+ let contentMinX = insets.left
198
+ let contentWidth = max(size.width - insets.left - insets.right, 0)
199
+
200
+ let headerMidY = insets.top + headerHeight / 2
201
+ rotateButton.frame = CGRect(
202
+ x: contentMinX + contentWidth - spacing + theme.metric(8) - iconButtonSize,
203
+ y: headerMidY - iconButtonSize / 2,
204
+ width: iconButtonSize,
205
+ height: iconButtonSize
206
+ )
207
+ // Centered, clear of the rotate button on both sides.
208
+ let titleInset = spacing + iconButtonSize
209
+ titleLabel.frame = CGRect(
210
+ x: contentMinX + titleInset,
211
+ y: insets.top,
212
+ width: max(contentWidth - titleInset * 2, 0),
213
+ height: headerHeight
214
+ )
215
+
216
+ let buttonY = size.height - bottomInset - spacing - buttonHeight
217
+ let buttonWidth = max((contentWidth - spacing * 2 - buttonGap) / 2, 0)
218
+ cancelButton.frame = CGRect(
219
+ x: contentMinX + spacing,
220
+ y: buttonY,
221
+ width: buttonWidth,
222
+ height: buttonHeight
223
+ )
224
+ confirmButton.frame = CGRect(
225
+ x: cancelButton.frame.maxX + buttonGap,
226
+ y: buttonY,
227
+ width: buttonWidth,
228
+ height: buttonHeight
229
+ )
230
+
231
+ // The crop view pads the crop box by `spacing` on every side.
232
+ let cropMinY = insets.top + headerHeight
233
+ return CGRect(
234
+ x: contentMinX,
235
+ y: cropMinY,
236
+ width: contentWidth,
237
+ height: max(buttonY - cropMinY, 0)
238
+ )
239
+ }
240
+
241
+ private func updateControls() {
242
+ cropView.isUserInteractionEnabled = !isProcessing
243
+ rotateButton.isEnabled = !isProcessing
244
+ confirmButton.isLoading = isProcessing
245
+ }
246
+
247
+ @objc private func rotateTapped() {
248
+ guard !isRotating, !isProcessing else {
249
+ return
250
+ }
251
+ isRotating = true
252
+ cropView.rotateImageNinetyDegrees(animated: true, clockwise: false) { [weak self] _ in
253
+ self?.isRotating = false
254
+ }
255
+ }
256
+
257
+ @objc private func cancelTapped() {
258
+ delegate?.imageCropperViewControllerDidCancel(self)
259
+ }
260
+
261
+ @objc private func confirmTapped() {
262
+ guard !isProcessing, !isRotating else {
263
+ return
264
+ }
265
+ delegate?.imageCropperViewController(
266
+ self,
267
+ didCropWithFrame: cropView.imageCropFrame,
268
+ angle: cropView.angle
269
+ )
270
+ }
271
+
272
+ private static func nonEmpty(_ value: String?) -> String? {
273
+ guard let value, !value.isEmpty else {
274
+ return nil
275
+ }
276
+ return value
277
+ }
278
+ }
279
+
280
+ // OneKey's large Button: a capsule with a 16pt medium label, a pressed color,
281
+ // and a spinner next to the label while loading.
282
+ final class ImageCropperButton: UIControl {
283
+ private let label = UILabel()
284
+ private let spinner = UIActivityIndicatorView(style: .medium)
285
+ private let horizontalPadding: CGFloat
286
+ private let spinnerSpacing: CGFloat
287
+ private let normalBackgroundColor: UIColor
288
+ private let pressedBackgroundColor: UIColor
289
+
290
+ var isLoading = false {
291
+ didSet {
292
+ guard isLoading != oldValue else {
293
+ return
294
+ }
295
+ if isLoading {
296
+ spinner.startAnimating()
297
+ } else {
298
+ spinner.stopAnimating()
299
+ }
300
+ updateAppearance()
301
+ setNeedsLayout()
302
+ }
303
+ }
304
+
305
+ override var isHighlighted: Bool {
306
+ didSet { updateAppearance() }
307
+ }
308
+
309
+ override var isEnabled: Bool {
310
+ didSet { updateAppearance() }
311
+ }
312
+
313
+ init(
314
+ title: String,
315
+ horizontalPadding: CGFloat,
316
+ spinnerSpacing: CGFloat,
317
+ font: UIFont,
318
+ textColor: UIColor,
319
+ backgroundColor: UIColor,
320
+ pressedBackgroundColor: UIColor
321
+ ) {
322
+ self.horizontalPadding = horizontalPadding
323
+ self.spinnerSpacing = spinnerSpacing
324
+ normalBackgroundColor = backgroundColor
325
+ self.pressedBackgroundColor = pressedBackgroundColor
326
+ super.init(frame: .zero)
327
+
328
+ layer.cornerCurve = .continuous
329
+ label.text = title
330
+ label.font = font
331
+ label.textColor = textColor
332
+ label.textAlignment = .center
333
+ label.lineBreakMode = .byTruncatingTail
334
+ label.isUserInteractionEnabled = false
335
+ addSubview(label)
336
+
337
+ spinner.color = textColor
338
+ spinner.hidesWhenStopped = true
339
+ spinner.isUserInteractionEnabled = false
340
+ addSubview(spinner)
341
+
342
+ isAccessibilityElement = true
343
+ accessibilityTraits = .button
344
+ accessibilityLabel = title
345
+ updateAppearance()
346
+ }
347
+
348
+ @available(*, unavailable)
349
+ required init?(coder: NSCoder) {
350
+ fatalError("init(coder:) has not been implemented")
351
+ }
352
+
353
+ override func layoutSubviews() {
354
+ super.layoutSubviews()
355
+ layer.cornerRadius = bounds.height / 2
356
+
357
+ let spinnerSize = spinner.intrinsicContentSize
358
+ let leadingWidth = isLoading ? spinnerSize.width + spinnerSpacing : 0
359
+ let maxLabelWidth = max(bounds.width - horizontalPadding * 2 - leadingWidth, 0)
360
+ let labelWidth = min(ceil(label.intrinsicContentSize.width), maxLabelWidth)
361
+ let startX = (bounds.width - labelWidth - leadingWidth) / 2
362
+
363
+ spinner.frame = CGRect(
364
+ x: startX,
365
+ y: (bounds.height - spinnerSize.height) / 2,
366
+ width: spinnerSize.width,
367
+ height: spinnerSize.height
368
+ )
369
+ label.frame = CGRect(
370
+ x: startX + leadingWidth,
371
+ y: 0,
372
+ width: labelWidth,
373
+ height: bounds.height
374
+ )
375
+ }
376
+
377
+ private func updateAppearance() {
378
+ backgroundColor = isHighlighted ? pressedBackgroundColor : normalBackgroundColor
379
+ // OneKey dims disabled and loading buttons alike.
380
+ alpha = isEnabled && !isLoading ? 1 : 0.4
381
+ accessibilityTraits = isEnabled ? .button : [.button, .notEnabled]
382
+ }
383
+ }
384
+
385
+ // OneKey's header icon button: a 24pt icon with a round pressed background.
386
+ final class ImageCropperIconButton: UIControl {
387
+ private let imageView = UIImageView()
388
+ private let pressedBackgroundColor: UIColor
389
+
390
+ override var isHighlighted: Bool {
391
+ didSet { updateAppearance() }
392
+ }
393
+
394
+ override var isEnabled: Bool {
395
+ didSet { updateAppearance() }
396
+ }
397
+
398
+ init(image: UIImage, tintColor: UIColor, pressedBackgroundColor: UIColor) {
399
+ self.pressedBackgroundColor = pressedBackgroundColor
400
+ super.init(frame: .zero)
401
+ imageView.image = image
402
+ imageView.tintColor = tintColor
403
+ imageView.contentMode = .center
404
+ imageView.isUserInteractionEnabled = false
405
+ addSubview(imageView)
406
+ isAccessibilityElement = true
407
+ accessibilityTraits = .button
408
+ updateAppearance()
409
+ }
410
+
411
+ @available(*, unavailable)
412
+ required init?(coder: NSCoder) {
413
+ fatalError("init(coder:) has not been implemented")
414
+ }
415
+
416
+ override func layoutSubviews() {
417
+ super.layoutSubviews()
418
+ layer.cornerRadius = bounds.height / 2
419
+ imageView.frame = bounds
420
+ }
421
+
422
+ private func updateAppearance() {
423
+ backgroundColor = isHighlighted ? pressedBackgroundColor : .clear
424
+ imageView.alpha = isEnabled ? 1 : 0.4
425
+ }
426
+ }
427
+
428
+ enum ImageCropperIcons {
429
+ // OneKey's RotateCounterclockwise icon (24x24 viewBox). Android uses the
430
+ // same path in res/drawable/image_crop_picker_rotate.xml.
431
+ static func rotateCounterclockwise(size: CGFloat) -> UIImage {
432
+ let path = UIBezierPath()
433
+ path.move(to: CGPoint(x: 6, y: 5.426))
434
+ path.addCurve(to: CGPoint(x: 12.028, y: 3), controlPoint1: CGPoint(x: 7.628, y: 3.919), controlPoint2: CGPoint(x: 9.484, y: 3))
435
+ path.addCurve(to: CGPoint(x: 20.969, y: 10.984), controlPoint1: CGPoint(x: 16.605, y: 3.001), controlPoint2: CGPoint(x: 20.452, y: 6.436))
436
+ path.addCurve(to: CGPoint(x: 14.047, y: 20.77), controlPoint1: CGPoint(x: 21.485, y: 15.531), controlPoint2: CGPoint(x: 18.507, y: 19.742))
437
+ path.addCurve(to: CGPoint(x: 3.541, y: 15), controlPoint1: CGPoint(x: 9.588, y: 21.798), controlPoint2: CGPoint(x: 5.067, y: 19.315))
438
+ path.addLine(to: CGPoint(x: 3.207, y: 14.057))
439
+ path.addLine(to: CGPoint(x: 5.093, y: 13.391))
440
+ path.addLine(to: CGPoint(x: 5.426, y: 14.333))
441
+ path.addCurve(to: CGPoint(x: 13.597, y: 18.821), controlPoint1: CGPoint(x: 6.612, y: 17.689), controlPoint2: CGPoint(x: 10.128, y: 19.62))
442
+ path.addCurve(to: CGPoint(x: 18.981, y: 11.211), controlPoint1: CGPoint(x: 17.066, y: 18.023), controlPoint2: CGPoint(x: 19.382, y: 14.748))
443
+ path.addCurve(to: CGPoint(x: 12.029, y: 5), controlPoint1: CGPoint(x: 18.58, y: 7.674), controlPoint2: CGPoint(x: 15.588, y: 5.002))
444
+ path.addCurve(to: CGPoint(x: 7.244, y: 7), controlPoint1: CGPoint(x: 10.047, y: 5), controlPoint2: CGPoint(x: 8.622, y: 5.686))
445
+ path.addLine(to: CGPoint(x: 10, y: 7))
446
+ path.addLine(to: CGPoint(x: 10, y: 9))
447
+ path.addLine(to: CGPoint(x: 4, y: 9))
448
+ path.addLine(to: CGPoint(x: 4, y: 3))
449
+ path.addLine(to: CGPoint(x: 6, y: 3))
450
+ path.close()
451
+ path.apply(CGAffineTransform(scaleX: size / 24, y: size / 24))
452
+
453
+ let format = UIGraphicsImageRendererFormat.preferred()
454
+ format.opaque = false
455
+ let image = UIGraphicsImageRenderer(size: CGSize(width: size, height: size), format: format)
456
+ .image { _ in
457
+ UIColor.black.setFill()
458
+ path.fill()
459
+ }
460
+ return image.withRenderingMode(.alwaysTemplate)
461
+ }
462
+ }
@@ -38,6 +38,15 @@ NS_ASSUME_NONNULL_BEGIN
38
38
  /** Shows and hides the interior grid lines with an optional crossfade animation. */
39
39
  - (void)setGridHidden:(BOOL)hidden animated:(BOOL)animated;
40
40
 
41
+ /** OneKey: color of the crop box border and corner handles. Default is white. */
42
+ @property (nonatomic, strong) UIColor *frameColor;
43
+
44
+ /** OneKey: color of the interior grid lines. Default is white. */
45
+ @property (nonatomic, strong) UIColor *gridColor;
46
+
47
+ /** OneKey: hides the corner handles, for crop boxes that cannot be resized. */
48
+ @property (nonatomic, assign) BOOL cornerHandlesHidden;
49
+
41
50
  @end
42
51
 
43
52
  NS_ASSUME_NONNULL_END
@@ -50,6 +50,9 @@ static const CGFloat kTOCropOverLayerCornerWidth = 20.0f;
50
50
  }
51
51
 
52
52
  - (void)setup {
53
+ _frameColor = [UIColor whiteColor];
54
+ _gridColor = [UIColor whiteColor];
55
+
53
56
  UIView * (^newLineView)(void) = ^UIView *(void) {
54
57
  return [self createNewLineView];
55
58
  };
@@ -188,6 +191,41 @@ static const CGFloat kTOCropOverLayerCornerWidth = 20.0f;
188
191
 
189
192
  #pragma mark - Property methods
190
193
 
194
+ - (NSArray<UIView *> *)cornerHandleViews {
195
+ NSMutableArray<UIView *> *views = [NSMutableArray array];
196
+ for (NSArray *lines in @[self.topLeftLineViews, self.topRightLineViews, self.bottomRightLineViews, self.bottomLeftLineViews]) {
197
+ [views addObjectsFromArray:lines];
198
+ }
199
+ return views;
200
+ }
201
+
202
+ - (void)setFrameColor:(UIColor *)frameColor {
203
+ _frameColor = frameColor ?: [UIColor whiteColor];
204
+ for (UIView *lineView in self.outerLineViews) {
205
+ lineView.backgroundColor = _frameColor;
206
+ }
207
+ for (UIView *lineView in [self cornerHandleViews]) {
208
+ lineView.backgroundColor = _frameColor;
209
+ }
210
+ }
211
+
212
+ - (void)setGridColor:(UIColor *)gridColor {
213
+ _gridColor = gridColor ?: [UIColor whiteColor];
214
+ for (UIView *lineView in self.horizontalGridLines) {
215
+ lineView.backgroundColor = _gridColor;
216
+ }
217
+ for (UIView *lineView in self.verticalGridLines) {
218
+ lineView.backgroundColor = _gridColor;
219
+ }
220
+ }
221
+
222
+ - (void)setCornerHandlesHidden:(BOOL)cornerHandlesHidden {
223
+ _cornerHandlesHidden = cornerHandlesHidden;
224
+ for (UIView *lineView in [self cornerHandleViews]) {
225
+ lineView.hidden = cornerHandlesHidden;
226
+ }
227
+ }
228
+
191
229
  - (void)setDisplayHorizontalGridLines:(BOOL)displayHorizontalGridLines {
192
230
  _displayHorizontalGridLines = displayHorizontalGridLines;
193
231
 
@@ -196,7 +234,7 @@ static const CGFloat kTOCropOverLayerCornerWidth = 20.0f;
196
234
  }];
197
235
 
198
236
  if (_displayHorizontalGridLines) {
199
- self.horizontalGridLines = @[[self createNewLineView], [self createNewLineView]];
237
+ self.horizontalGridLines = @[[self createGridLineView], [self createGridLineView]];
200
238
  } else {
201
239
  self.horizontalGridLines = @[];
202
240
  }
@@ -214,7 +252,7 @@ static const CGFloat kTOCropOverLayerCornerWidth = 20.0f;
214
252
  }];
215
253
 
216
254
  if (_displayVerticalGridLines) {
217
- self.verticalGridLines = @[[self createNewLineView], [self createNewLineView]];
255
+ self.verticalGridLines = @[[self createGridLineView], [self createGridLineView]];
218
256
  } else {
219
257
  self.verticalGridLines = @[];
220
258
  }
@@ -232,9 +270,15 @@ static const CGFloat kTOCropOverLayerCornerWidth = 20.0f;
232
270
 
233
271
  - (nonnull UIView *)createNewLineView {
234
272
  UIView *newLine = [[UIView alloc] initWithFrame:CGRectZero];
235
- newLine.backgroundColor = [UIColor whiteColor];
273
+ newLine.backgroundColor = self.frameColor ?: [UIColor whiteColor];
236
274
  [self addSubview:newLine];
237
275
  return newLine;
238
276
  }
239
277
 
278
+ - (nonnull UIView *)createGridLineView {
279
+ UIView *newLine = [self createNewLineView];
280
+ newLine.backgroundColor = self.gridColor ?: [UIColor whiteColor];
281
+ return newLine;
282
+ }
283
+
240
284
  @end
@@ -15,6 +15,22 @@ export interface PickedImage {
15
15
  cropRect?: CropRect;
16
16
  filename?: string;
17
17
  }
18
+ export type ImageCropperColorScheme = 'light' | 'dark';
19
+ export interface ImageCropperAppearance {
20
+ colorScheme?: ImageCropperColorScheme;
21
+ backgroundColor?: string;
22
+ titleColor?: string;
23
+ iconColor?: string;
24
+ cancelButtonColor?: string;
25
+ cancelButtonPressedColor?: string;
26
+ cancelButtonTextColor?: string;
27
+ confirmButtonColor?: string;
28
+ confirmButtonPressedColor?: string;
29
+ confirmButtonTextColor?: string;
30
+ titleFontFamily?: string;
31
+ buttonFontFamily?: string;
32
+ scale?: number;
33
+ }
18
34
  export interface ImageCropPickerOptions {
19
35
  width?: number;
20
36
  height?: number;
@@ -28,19 +44,9 @@ export interface ImageCropPickerOptions {
28
44
  cropperToolbarTitle?: string;
29
45
  cropperChooseText?: string;
30
46
  cropperCancelText?: string;
31
- cropperChooseColor?: string;
32
- cropperCancelColor?: string;
33
47
  cropperRotateButtonsHidden?: boolean;
34
- cropperActiveWidgetColor?: string;
35
- cropperToolbarColor?: string;
36
- cropperToolbarWidgetColor?: string;
37
- cropperStatusBarLight?: boolean;
38
- cropperNavigationBarLight?: boolean;
39
48
  showCropGuidelines?: boolean;
40
- showCropFrame?: boolean;
41
- enableRotationGesture?: boolean;
42
- hideBottomControls?: boolean;
43
- disableCropperColorSetters?: boolean;
49
+ cropperAppearance?: ImageCropperAppearance;
44
50
  }
45
51
  export interface ReactNativeImageCropPicker extends HybridObject<{
46
52
  ios: 'swift';
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeImageCropPicker.nitro.d.ts","sourceRoot":"","sources":["../../../src/ReactNativeImageCropPicker.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAE1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IAEb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IAErC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,0BACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAGzD,UAAU,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAIlE,WAAW,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAAC;IAGxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
1
+ {"version":3,"file":"ReactNativeImageCropPicker.nitro.d.ts","sourceRoot":"","sources":["../../../src/ReactNativeImageCropPicker.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAE1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IAEb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,MAAM,CAAC;AAKvD,MAAM,WAAW,sBAAsB;IAErC,WAAW,CAAC,EAAE,uBAAuB,CAAC;IAEtC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IAErC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;CAC5C;AAED,MAAM,WAAW,0BACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAGzD,UAAU,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAIlE,WAAW,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAAC;IAGxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
@@ -13,6 +13,10 @@ namespace margelo::nitro::reactnativeimagecroppicker { struct PickedImage; }
13
13
  namespace margelo::nitro::reactnativeimagecroppicker { struct CropRect; }
14
14
  // Forward declaration of `ImageCropPickerOptions` to properly resolve imports.
15
15
  namespace margelo::nitro::reactnativeimagecroppicker { struct ImageCropPickerOptions; }
16
+ // Forward declaration of `ImageCropperAppearance` to properly resolve imports.
17
+ namespace margelo::nitro::reactnativeimagecroppicker { struct ImageCropperAppearance; }
18
+ // Forward declaration of `ImageCropperColorScheme` to properly resolve imports.
19
+ namespace margelo::nitro::reactnativeimagecroppicker { enum class ImageCropperColorScheme; }
16
20
 
17
21
  #include "PickedImage.hpp"
18
22
  #include <NitroModules/Promise.hpp>
@@ -25,6 +29,10 @@ namespace margelo::nitro::reactnativeimagecroppicker { struct ImageCropPickerOpt
25
29
  #include <NitroModules/JUnit.hpp>
26
30
  #include "ImageCropPickerOptions.hpp"
27
31
  #include "JImageCropPickerOptions.hpp"
32
+ #include "ImageCropperAppearance.hpp"
33
+ #include "JImageCropperAppearance.hpp"
34
+ #include "ImageCropperColorScheme.hpp"
35
+ #include "JImageCropperColorScheme.hpp"
28
36
 
29
37
  namespace margelo::nitro::reactnativeimagecroppicker {
30
38