@onekeyfe/react-native-image-crop-picker 3.0.147 → 3.0.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -5
- package/ReactNativeImageCropPicker.podspec +5 -3
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerImageProcessor.kt +8 -18
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerSession.kt +21 -46
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperActivity.kt +636 -0
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperTheme.kt +188 -0
- package/android/src/main/res/drawable/image_crop_picker_rotate.xml +11 -0
- package/ios/ImageCropPickerImageProcessor.swift +8 -4
- package/ios/ImageCropPickerSession.swift +43 -46
- package/ios/ImageCropperTheme.swift +155 -0
- package/ios/ImageCropperViewController.swift +462 -0
- package/ios/TOCropViewController/Views/TOCropOverlayView.h +9 -0
- package/ios/TOCropViewController/Views/TOCropOverlayView.m +47 -3
- package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts +17 -11
- package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridReactNativeImageCropPickerSpec.cpp +8 -0
- package/nitrogen/generated/android/c++/JImageCropPickerOptions.hpp +9 -45
- package/nitrogen/generated/android/c++/JImageCropperAppearance.hpp +108 -0
- package/nitrogen/generated/android/c++/JImageCropperColorScheme.hpp +58 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerOptions.kt +5 -55
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperAppearance.kt +111 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperColorScheme.kt +23 -0
- package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Bridge.hpp +36 -0
- package/nitrogen/generated/ios/ReactNativeImageCropPicker-Swift-Cxx-Umbrella.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridReactNativeImageCropPickerSpecSwift.hpp +6 -0
- package/nitrogen/generated/ios/swift/ImageCropPickerOptions.swift +6 -193
- package/nitrogen/generated/ios/swift/ImageCropperAppearance.swift +251 -0
- package/nitrogen/generated/ios/swift/ImageCropperColorScheme.swift +40 -0
- package/nitrogen/generated/shared/c++/ImageCropPickerOptions.hpp +8 -46
- package/nitrogen/generated/shared/c++/ImageCropperAppearance.hpp +134 -0
- package/nitrogen/generated/shared/c++/ImageCropperColorScheme.hpp +76 -0
- package/package.json +1 -1
- package/src/ReactNativeImageCropPicker.nitro.ts +29 -13
package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropperTheme.kt
ADDED
|
@@ -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:
|
|
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
|
-
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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:
|
|
353
|
-
func
|
|
354
|
-
_
|
|
355
|
-
|
|
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
|
-
|
|
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: (
|
|
370
|
-
y: (
|
|
371
|
-
width: (
|
|
372
|
-
height: (
|
|
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
|
-
|
|
378
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|