@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,188 @@
1
+ package com.margelo.nitro.reactnativeimagecroppicker
2
+
3
+ import android.content.Intent
4
+ import android.graphics.Color
5
+
6
+ // Resolved colors, fonts and scale of the cropper screen. The layout matches
7
+ // the iOS ImageCropperViewController. Colors are ARGB ints.
8
+ internal data class ImageCropperTheme(
9
+ val isDark: Boolean,
10
+ val backgroundColor: Int,
11
+ val titleColor: Int,
12
+ val iconColor: Int,
13
+ val cancelButtonColor: Int,
14
+ val cancelButtonPressedColor: Int,
15
+ val cancelButtonTextColor: Int,
16
+ val confirmButtonColor: Int,
17
+ val confirmButtonPressedColor: Int,
18
+ val confirmButtonTextColor: Int,
19
+ val titleFontFamily: String?,
20
+ val buttonFontFamily: String?,
21
+ val scale: Float,
22
+ ) {
23
+ fun writeTo(intent: Intent) {
24
+ intent.putExtra(EXTRA_IS_DARK, isDark)
25
+ .putExtra(EXTRA_BACKGROUND_COLOR, backgroundColor)
26
+ .putExtra(EXTRA_TITLE_COLOR, titleColor)
27
+ .putExtra(EXTRA_ICON_COLOR, iconColor)
28
+ .putExtra(EXTRA_CANCEL_BUTTON_COLOR, cancelButtonColor)
29
+ .putExtra(EXTRA_CANCEL_BUTTON_PRESSED_COLOR, cancelButtonPressedColor)
30
+ .putExtra(EXTRA_CANCEL_BUTTON_TEXT_COLOR, cancelButtonTextColor)
31
+ .putExtra(EXTRA_CONFIRM_BUTTON_COLOR, confirmButtonColor)
32
+ .putExtra(EXTRA_CONFIRM_BUTTON_PRESSED_COLOR, confirmButtonPressedColor)
33
+ .putExtra(EXTRA_CONFIRM_BUTTON_TEXT_COLOR, confirmButtonTextColor)
34
+ .putExtra(EXTRA_TITLE_FONT_FAMILY, titleFontFamily)
35
+ .putExtra(EXTRA_BUTTON_FONT_FAMILY, buttonFontFamily)
36
+ .putExtra(EXTRA_SCALE, scale)
37
+ }
38
+
39
+ // OneKey's tokens: $bgApp, $text, $icon, $bgStrong, $bgStrongActive,
40
+ // $bgPrimary, $bgPrimaryActive and $textInverse.
41
+ private class Palette(
42
+ val background: String,
43
+ val title: String,
44
+ val icon: String,
45
+ val cancelButton: String,
46
+ val cancelButtonPressed: String,
47
+ val cancelButtonText: String,
48
+ val confirmButton: String,
49
+ val confirmButtonPressed: String,
50
+ val confirmButtonText: String,
51
+ )
52
+
53
+ companion object {
54
+ private const val PREFIX = "com.margelo.nitro.reactnativeimagecroppicker.theme."
55
+ private const val EXTRA_IS_DARK = "${PREFIX}isDark"
56
+ private const val EXTRA_BACKGROUND_COLOR = "${PREFIX}backgroundColor"
57
+ private const val EXTRA_TITLE_COLOR = "${PREFIX}titleColor"
58
+ private const val EXTRA_ICON_COLOR = "${PREFIX}iconColor"
59
+ private const val EXTRA_CANCEL_BUTTON_COLOR = "${PREFIX}cancelButtonColor"
60
+ private const val EXTRA_CANCEL_BUTTON_PRESSED_COLOR = "${PREFIX}cancelButtonPressedColor"
61
+ private const val EXTRA_CANCEL_BUTTON_TEXT_COLOR = "${PREFIX}cancelButtonTextColor"
62
+ private const val EXTRA_CONFIRM_BUTTON_COLOR = "${PREFIX}confirmButtonColor"
63
+ private const val EXTRA_CONFIRM_BUTTON_PRESSED_COLOR = "${PREFIX}confirmButtonPressedColor"
64
+ private const val EXTRA_CONFIRM_BUTTON_TEXT_COLOR = "${PREFIX}confirmButtonTextColor"
65
+ private const val EXTRA_TITLE_FONT_FAMILY = "${PREFIX}titleFontFamily"
66
+ private const val EXTRA_BUTTON_FONT_FAMILY = "${PREFIX}buttonFontFamily"
67
+ private const val EXTRA_SCALE = "${PREFIX}scale"
68
+
69
+ private val lightPalette = Palette(
70
+ background = "#FFFFFF",
71
+ title = "#000000DF",
72
+ icon = "#0000009B",
73
+ cancelButton = "#0000000F",
74
+ cancelButtonPressed = "#0000001F",
75
+ cancelButtonText = "#000000DF",
76
+ confirmButton = "#000000DF",
77
+ confirmButtonPressed = "#0000009B",
78
+ confirmButtonText = "#FFFFFFED",
79
+ )
80
+
81
+ private val darkPalette = Palette(
82
+ background = "#0F0F0F",
83
+ title = "#FFFFFFED",
84
+ icon = "#FFFFFFAF",
85
+ cancelButton = "#FFFFFF12",
86
+ cancelButtonPressed = "#FFFFFF22",
87
+ cancelButtonText = "#FFFFFFED",
88
+ confirmButton = "#FFFFFFED",
89
+ confirmButtonPressed = "#FFFFFF72",
90
+ confirmButtonText = "#000000DF",
91
+ )
92
+
93
+ fun resolve(appearance: ImageCropperAppearance?, systemIsDark: Boolean): ImageCropperTheme {
94
+ val isDark = when (appearance?.colorScheme) {
95
+ ImageCropperColorScheme.DARK -> true
96
+ ImageCropperColorScheme.LIGHT -> false
97
+ null -> systemIsDark
98
+ }
99
+ val palette = if (isDark) darkPalette else lightPalette
100
+ fun color(value: String?, fallback: String): Int =
101
+ parseCssColor(value) ?: parseCssColor(fallback) ?: Color.TRANSPARENT
102
+
103
+ val scale = appearance?.scale?.toFloat()?.takeIf { it.isFinite() && it > 0f } ?: 1f
104
+ return ImageCropperTheme(
105
+ isDark = isDark,
106
+ backgroundColor = color(appearance?.backgroundColor, palette.background),
107
+ titleColor = color(appearance?.titleColor, palette.title),
108
+ iconColor = color(appearance?.iconColor, palette.icon),
109
+ cancelButtonColor = color(appearance?.cancelButtonColor, palette.cancelButton),
110
+ cancelButtonPressedColor = color(
111
+ appearance?.cancelButtonPressedColor,
112
+ palette.cancelButtonPressed,
113
+ ),
114
+ cancelButtonTextColor = color(appearance?.cancelButtonTextColor, palette.cancelButtonText),
115
+ confirmButtonColor = color(appearance?.confirmButtonColor, palette.confirmButton),
116
+ confirmButtonPressedColor = color(
117
+ appearance?.confirmButtonPressedColor,
118
+ palette.confirmButtonPressed,
119
+ ),
120
+ confirmButtonTextColor = color(
121
+ appearance?.confirmButtonTextColor,
122
+ palette.confirmButtonText,
123
+ ),
124
+ titleFontFamily = appearance?.titleFontFamily?.takeIf { it.isNotEmpty() },
125
+ buttonFontFamily = appearance?.buttonFontFamily?.takeIf { it.isNotEmpty() },
126
+ scale = scale,
127
+ )
128
+ }
129
+
130
+ fun readFrom(intent: Intent): ImageCropperTheme {
131
+ val fallback = resolve(null, systemIsDark = intent.getBooleanExtra(EXTRA_IS_DARK, false))
132
+ return ImageCropperTheme(
133
+ isDark = fallback.isDark,
134
+ backgroundColor = intent.getIntExtra(EXTRA_BACKGROUND_COLOR, fallback.backgroundColor),
135
+ titleColor = intent.getIntExtra(EXTRA_TITLE_COLOR, fallback.titleColor),
136
+ iconColor = intent.getIntExtra(EXTRA_ICON_COLOR, fallback.iconColor),
137
+ cancelButtonColor = intent.getIntExtra(EXTRA_CANCEL_BUTTON_COLOR, fallback.cancelButtonColor),
138
+ cancelButtonPressedColor = intent.getIntExtra(
139
+ EXTRA_CANCEL_BUTTON_PRESSED_COLOR,
140
+ fallback.cancelButtonPressedColor,
141
+ ),
142
+ cancelButtonTextColor = intent.getIntExtra(
143
+ EXTRA_CANCEL_BUTTON_TEXT_COLOR,
144
+ fallback.cancelButtonTextColor,
145
+ ),
146
+ confirmButtonColor = intent.getIntExtra(EXTRA_CONFIRM_BUTTON_COLOR, fallback.confirmButtonColor),
147
+ confirmButtonPressedColor = intent.getIntExtra(
148
+ EXTRA_CONFIRM_BUTTON_PRESSED_COLOR,
149
+ fallback.confirmButtonPressedColor,
150
+ ),
151
+ confirmButtonTextColor = intent.getIntExtra(
152
+ EXTRA_CONFIRM_BUTTON_TEXT_COLOR,
153
+ fallback.confirmButtonTextColor,
154
+ ),
155
+ titleFontFamily = intent.getStringExtra(EXTRA_TITLE_FONT_FAMILY),
156
+ buttonFontFamily = intent.getStringExtra(EXTRA_BUTTON_FONT_FAMILY),
157
+ scale = intent.getFloatExtra(EXTRA_SCALE, fallback.scale),
158
+ )
159
+ }
160
+
161
+ // Parses CSS hex colors: #RGB, #RRGGBB and #RRGGBBAA. Color.parseColor
162
+ // reads 8 digits as #AARRGGBB, so it cannot be used here.
163
+ fun parseCssColor(value: String?): Int? {
164
+ var hex = value?.trim()?.removePrefix("#") ?: return null
165
+ if (hex.length == 3) {
166
+ hex = hex.map { "$it$it" }.joinToString("")
167
+ }
168
+ if (hex.length != 6 && hex.length != 8) {
169
+ return null
170
+ }
171
+ val rgba = hex.toLongOrNull(16) ?: return null
172
+ return if (hex.length == 8) {
173
+ Color.argb(
174
+ (rgba and 0xff).toInt(),
175
+ ((rgba shr 24) and 0xff).toInt(),
176
+ ((rgba shr 16) and 0xff).toInt(),
177
+ ((rgba shr 8) and 0xff).toInt(),
178
+ )
179
+ } else {
180
+ Color.rgb(
181
+ ((rgba shr 16) and 0xff).toInt(),
182
+ ((rgba shr 8) and 0xff).toInt(),
183
+ (rgba and 0xff).toInt(),
184
+ )
185
+ }
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- OneKey's RotateCounterclockwise icon. iOS draws the same path in ImageCropperIcons. -->
3
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
4
+ android:width="24dp"
5
+ android:height="24dp"
6
+ android:viewportWidth="24"
7
+ android:viewportHeight="24">
8
+ <path
9
+ android:fillColor="#FF000000"
10
+ android:pathData="M6 5.426C7.628 3.919 9.484 3 12.028 3a9 9 0 1 1-8.487 12l-.334-.943 1.886-.666.333.942A7 7 0 1 0 12.029 5c-1.982 0-3.407.686-4.785 2H10v2H4V3h2z" />
11
+ </vector>
@@ -16,9 +16,9 @@ struct ImageCropPickerConfig {
16
16
  let cropperToolbarTitle: String?
17
17
  let cropperChooseText: String?
18
18
  let cropperCancelText: String?
19
- let cropperChooseColor: String?
20
- let cropperCancelColor: String?
21
19
  let cropperRotateButtonsHidden: Bool
20
+ let showCropGuidelines: Bool
21
+ let appearance: ImageCropperAppearanceConfig
22
22
 
23
23
  init(_ options: ImageCropPickerOptions, forceCropping: Bool = false) {
24
24
  width = options.width
@@ -33,9 +33,9 @@ struct ImageCropPickerConfig {
33
33
  cropperToolbarTitle = options.cropperToolbarTitle
34
34
  cropperChooseText = options.cropperChooseText
35
35
  cropperCancelText = options.cropperCancelText
36
- cropperChooseColor = options.cropperChooseColor
37
- cropperCancelColor = options.cropperCancelColor
38
36
  cropperRotateButtonsHidden = options.cropperRotateButtonsHidden ?? false
37
+ showCropGuidelines = options.showCropGuidelines ?? true
38
+ appearance = ImageCropperAppearanceConfig(options.cropperAppearance)
39
39
  }
40
40
 
41
41
  var targetSize: CGSize? {
@@ -305,6 +305,7 @@ enum ImageCropPickerImageProcessor {
305
305
  }
306
306
  }
307
307
 
308
+ // Parses CSS hex colors: #RGB, #RRGGBB and #RRGGBBAA.
308
309
  static func color(fromHex hex: String?) -> UIColor? {
309
310
  guard var value = hex?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
310
311
  return nil
@@ -312,6 +313,9 @@ enum ImageCropPickerImageProcessor {
312
313
  if value.hasPrefix("#") {
313
314
  value.removeFirst()
314
315
  }
316
+ if value.count == 3 {
317
+ value = value.map { "\($0)\($0)" }.joined()
318
+ }
315
319
  guard value.count == 6 || value.count == 8, let rgba = UInt64(value, radix: 16) else {
316
320
  return nil
317
321
  }
@@ -23,9 +23,10 @@ final class ImageCropPickerSession: NSObject {
23
23
 
24
24
  // Main thread state.
25
25
  private var pickerController: PHPickerViewController?
26
- private var cropController: TOCropViewController?
26
+ private var cropController: ImageCropperViewController?
27
27
  private var loadingView: UIView?
28
28
  private var pickedFilename: String?
29
+ private var pickedAssetIdentifier: String?
29
30
  private var sourceScale: CGFloat = 1
30
31
  private var presentationRequestedAt: Date?
31
32
  private var hasPresented = false
@@ -100,7 +101,9 @@ final class ImageCropPickerSession: NSObject {
100
101
 
101
102
  // PHPickerViewController runs out of process and needs no photo library
102
103
  // permission, so a previously denied permission can't block the picker.
103
- var configuration = PHPickerConfiguration()
104
+ // Passing the library only adds asset identifiers to the results; it
105
+ // doesn't ask for access either.
106
+ var configuration = PHPickerConfiguration(photoLibrary: .shared())
104
107
  configuration.filter = .images
105
108
  configuration.selectionLimit = 1
106
109
  configuration.preferredAssetRepresentationMode = .current
@@ -108,6 +111,14 @@ final class ImageCropPickerSession: NSObject {
108
111
  let picker = PHPickerViewController(configuration: configuration)
109
112
  picker.delegate = self
110
113
  picker.modalPresentationStyle = .fullScreen
114
+ switch config.appearance.colorScheme {
115
+ case .dark:
116
+ picker.overrideUserInterfaceStyle = .dark
117
+ case .light:
118
+ picker.overrideUserInterfaceStyle = .light
119
+ case nil:
120
+ break
121
+ }
111
122
  pickerController = picker
112
123
  present(picker, from: presenter)
113
124
  }
@@ -202,32 +213,12 @@ final class ImageCropPickerSession: NSObject {
202
213
  }
203
214
 
204
215
  private func presentCropper(image: UIImage, from presenter: UIViewController) {
205
- let controller: TOCropViewController
206
- if config.cropperCircleOverlay {
207
- controller = TOCropViewController(croppingStyle: .circular, image: image)
208
- } else {
209
- controller = TOCropViewController(image: image)
210
- if let targetSize = config.targetSize {
211
- controller.aspectRatioPreset = targetSize
212
- }
213
- controller.aspectRatioLockEnabled = !config.freeStyleCropEnabled
214
- controller.resetAspectRatioEnabled = !controller.aspectRatioLockEnabled
215
- }
216
-
217
- controller.title = config.cropperToolbarTitle
216
+ let theme = ImageCropperTheme(
217
+ config.appearance,
218
+ systemIsDark: presenter.traitCollection.userInterfaceStyle == .dark
219
+ )
220
+ let controller = ImageCropperViewController(image: image, config: config, theme: theme)
218
221
  controller.delegate = self
219
- if let color = ImageCropPickerImageProcessor.color(fromHex: config.cropperChooseColor) {
220
- controller.doneButtonColor = color
221
- }
222
- if let color = ImageCropPickerImageProcessor.color(fromHex: config.cropperCancelColor) {
223
- controller.cancelButtonColor = color
224
- }
225
- controller.doneButtonTitle = config.cropperChooseText
226
- controller.cancelButtonTitle = config.cropperCancelText
227
- controller.rotateButtonsHidden = config.cropperRotateButtonsHidden
228
- controller.modalPresentationStyle = .fullScreen
229
- controller.modalTransitionStyle = .coverVertical
230
-
231
222
  cropController = controller
232
223
  present(controller, from: presenter)
233
224
  }
@@ -345,37 +336,42 @@ extension ImageCropPickerSession: PHPickerViewControllerDelegate {
345
336
  dismissAll { self.finish(.failure(ImageCropPickerError.cancelled)) }
346
337
  return
347
338
  }
339
+ pickedAssetIdentifier = result.assetIdentifier
348
340
  loadPickedImage(result, in: picker)
349
341
  }
350
342
  }
351
343
 
352
- extension ImageCropPickerSession: TOCropViewControllerDelegate {
353
- func cropViewController(
354
- _ cropViewController: TOCropViewController,
355
- didCropTo image: UIImage,
356
- with cropRect: CGRect,
344
+ extension ImageCropPickerSession: ImageCropperViewControllerDelegate {
345
+ func imageCropperViewController(
346
+ _ controller: ImageCropperViewController,
347
+ didCropWithFrame cropFrame: CGRect,
357
348
  angle: Int
358
349
  ) {
359
350
  guard !isFinished, !isBusy else {
360
351
  return
361
352
  }
362
353
  isBusy = true
363
- showLoading(in: cropViewController.view)
354
+ controller.isProcessing = true
364
355
 
356
+ let image = controller.image
365
357
  let config = self.config
366
358
  let filename = pickedFilename
367
359
  // Report the crop rect in the coordinates of the original, undownsampled image.
368
360
  let sourceCropRect = CGRect(
369
- x: (cropRect.origin.x * sourceScale).rounded(),
370
- y: (cropRect.origin.y * sourceScale).rounded(),
371
- width: (cropRect.width * sourceScale).rounded(),
372
- height: (cropRect.height * sourceScale).rounded()
361
+ x: (cropFrame.origin.x * sourceScale).rounded(),
362
+ y: (cropFrame.origin.y * sourceScale).rounded(),
363
+ width: (cropFrame.width * sourceScale).rounded(),
364
+ height: (cropFrame.height * sourceScale).rounded()
373
365
  )
374
366
 
375
367
  DispatchQueue.global(qos: .userInitiated).async { [self] in
376
368
  let result = Result {
377
- try ImageCropPickerImageProcessor.makeResult(
378
- image: ImageCropPickerImageProcessor.resizeCroppedImage(image, config: config),
369
+ let isWholeImage = angle == 0 && cropFrame == CGRect(origin: .zero, size: image.size)
370
+ let cropped = isWholeImage
371
+ ? image
372
+ : image.croppedImage(withFrame: cropFrame, angle: angle, circularClip: false)
373
+ return try ImageCropPickerImageProcessor.makeResult(
374
+ image: ImageCropPickerImageProcessor.resizeCroppedImage(cropped, config: config),
379
375
  config: config,
380
376
  cropRect: sourceCropRect,
381
377
  filename: filename
@@ -383,23 +379,24 @@ extension ImageCropPickerSession: TOCropViewControllerDelegate {
383
379
  }
384
380
  DispatchQueue.main.async { [self] in
385
381
  isBusy = false
386
- hideLoading()
387
382
  dismissAll { self.finish(result) }
388
383
  }
389
384
  }
390
385
  }
391
386
 
392
- func cropViewController(
393
- _ cropViewController: TOCropViewController,
394
- didFinishCancelled cancelled: Bool
395
- ) {
387
+ func imageCropperViewControllerDidCancel(_ controller: ImageCropperViewController) {
396
388
  guard !isFinished, !isBusy else {
397
389
  return
398
390
  }
399
- if isPickerMode, pickerController?.presentingViewController != nil {
391
+ if isPickerMode, let picker = pickerController, picker.presentingViewController != nil {
400
392
  // Go back to the photo picker, like react-native-image-crop-picker.
393
+ // The picker keeps the photo selected, so without this, tapping the
394
+ // same photo again would only deselect it.
395
+ if #available(iOS 17.0, *), let identifier = pickedAssetIdentifier {
396
+ picker.deselectAssets(withIdentifiers: [identifier])
397
+ }
401
398
  cropController = nil
402
- cropViewController.dismiss(animated: true)
399
+ controller.dismiss(animated: true)
403
400
  return
404
401
  }
405
402
  dismissAll { self.finish(.failure(ImageCropPickerError.cancelled)) }
@@ -0,0 +1,155 @@
1
+ import UIKit
2
+
3
+ // Plain Swift copy of `ImageCropperAppearance`.
4
+ struct ImageCropperAppearanceConfig {
5
+ let colorScheme: ImageCropperColorScheme?
6
+ let backgroundColor: String?
7
+ let titleColor: String?
8
+ let iconColor: String?
9
+ let cancelButtonColor: String?
10
+ let cancelButtonPressedColor: String?
11
+ let cancelButtonTextColor: String?
12
+ let confirmButtonColor: String?
13
+ let confirmButtonPressedColor: String?
14
+ let confirmButtonTextColor: String?
15
+ let titleFontFamily: String?
16
+ let buttonFontFamily: String?
17
+ let scale: Double?
18
+
19
+ init(_ appearance: ImageCropperAppearance?) {
20
+ colorScheme = appearance?.colorScheme
21
+ backgroundColor = appearance?.backgroundColor
22
+ titleColor = appearance?.titleColor
23
+ iconColor = appearance?.iconColor
24
+ cancelButtonColor = appearance?.cancelButtonColor
25
+ cancelButtonPressedColor = appearance?.cancelButtonPressedColor
26
+ cancelButtonTextColor = appearance?.cancelButtonTextColor
27
+ confirmButtonColor = appearance?.confirmButtonColor
28
+ confirmButtonPressedColor = appearance?.confirmButtonPressedColor
29
+ confirmButtonTextColor = appearance?.confirmButtonTextColor
30
+ titleFontFamily = appearance?.titleFontFamily
31
+ buttonFontFamily = appearance?.buttonFontFamily
32
+ scale = appearance?.scale
33
+ }
34
+ }
35
+
36
+ // Resolved colors, fonts and metrics of the cropper screen. The layout
37
+ // matches Android's ImageCropperActivity.
38
+ struct ImageCropperTheme {
39
+ let isDark: Bool
40
+ let backgroundColor: UIColor
41
+ let titleColor: UIColor
42
+ let iconColor: UIColor
43
+ let cancelButtonColor: UIColor
44
+ let cancelButtonPressedColor: UIColor
45
+ let cancelButtonTextColor: UIColor
46
+ let confirmButtonColor: UIColor
47
+ let confirmButtonPressedColor: UIColor
48
+ let confirmButtonTextColor: UIColor
49
+ let titleFont: UIFont
50
+ let buttonFont: UIFont
51
+ let scale: CGFloat
52
+
53
+ // OneKey's tokens: $bgApp, $text, $icon, $bgStrong, $bgStrongActive,
54
+ // $bgPrimary, $bgPrimaryActive and $textInverse.
55
+ private struct Palette {
56
+ let background: String
57
+ let title: String
58
+ let icon: String
59
+ let cancelButton: String
60
+ let cancelButtonPressed: String
61
+ let cancelButtonText: String
62
+ let confirmButton: String
63
+ let confirmButtonPressed: String
64
+ let confirmButtonText: String
65
+ }
66
+
67
+ private static let lightPalette = Palette(
68
+ background: "#FFFFFF",
69
+ title: "#000000DF",
70
+ icon: "#0000009B",
71
+ cancelButton: "#0000000F",
72
+ cancelButtonPressed: "#0000001F",
73
+ cancelButtonText: "#000000DF",
74
+ confirmButton: "#000000DF",
75
+ confirmButtonPressed: "#0000009B",
76
+ confirmButtonText: "#FFFFFFED"
77
+ )
78
+
79
+ private static let darkPalette = Palette(
80
+ background: "#0F0F0F",
81
+ title: "#FFFFFFED",
82
+ icon: "#FFFFFFAF",
83
+ cancelButton: "#FFFFFF12",
84
+ cancelButtonPressed: "#FFFFFF22",
85
+ cancelButtonText: "#FFFFFFED",
86
+ confirmButton: "#FFFFFFED",
87
+ confirmButtonPressed: "#FFFFFF72",
88
+ confirmButtonText: "#000000DF"
89
+ )
90
+
91
+ init(_ appearance: ImageCropperAppearanceConfig, systemIsDark: Bool) {
92
+ switch appearance.colorScheme {
93
+ case .dark:
94
+ isDark = true
95
+ case .light:
96
+ isDark = false
97
+ case nil:
98
+ isDark = systemIsDark
99
+ }
100
+ let palette = isDark ? Self.darkPalette : Self.lightPalette
101
+
102
+ func color(_ value: String?, fallback: String) -> UIColor {
103
+ return ImageCropPickerImageProcessor.color(fromHex: value)
104
+ ?? ImageCropPickerImageProcessor.color(fromHex: fallback)
105
+ ?? .clear
106
+ }
107
+
108
+ backgroundColor = color(appearance.backgroundColor, fallback: palette.background)
109
+ titleColor = color(appearance.titleColor, fallback: palette.title)
110
+ iconColor = color(appearance.iconColor, fallback: palette.icon)
111
+ cancelButtonColor = color(appearance.cancelButtonColor, fallback: palette.cancelButton)
112
+ cancelButtonPressedColor = color(
113
+ appearance.cancelButtonPressedColor,
114
+ fallback: palette.cancelButtonPressed
115
+ )
116
+ cancelButtonTextColor = color(
117
+ appearance.cancelButtonTextColor,
118
+ fallback: palette.cancelButtonText
119
+ )
120
+ confirmButtonColor = color(appearance.confirmButtonColor, fallback: palette.confirmButton)
121
+ confirmButtonPressedColor = color(
122
+ appearance.confirmButtonPressedColor,
123
+ fallback: palette.confirmButtonPressed
124
+ )
125
+ confirmButtonTextColor = color(
126
+ appearance.confirmButtonTextColor,
127
+ fallback: palette.confirmButtonText
128
+ )
129
+
130
+ let resolvedScale = CGFloat(appearance.scale ?? 1)
131
+ scale = resolvedScale > 0 ? resolvedScale : 1
132
+ // OneKey's $headingLg and $bodyLgMedium, rounded like its scaled fonts.
133
+ titleFont = Self.font(
134
+ named: appearance.titleFontFamily,
135
+ size: (18 * scale).rounded(),
136
+ fallbackWeight: .semibold
137
+ )
138
+ buttonFont = Self.font(
139
+ named: appearance.buttonFontFamily,
140
+ size: (16 * scale).rounded(),
141
+ fallbackWeight: .medium
142
+ )
143
+ }
144
+
145
+ func metric(_ value: CGFloat) -> CGFloat {
146
+ return (value * scale).rounded()
147
+ }
148
+
149
+ private static func font(named name: String?, size: CGFloat, fallbackWeight: UIFont.Weight) -> UIFont {
150
+ if let name, let font = UIFont(name: name, size: size) {
151
+ return font
152
+ }
153
+ return .systemFont(ofSize: size, weight: fallbackWeight)
154
+ }
155
+ }