@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/README.md
CHANGED
|
@@ -4,16 +4,18 @@ Single photo picker and cropper for OneKey, built on [Nitro Modules](https://nit
|
|
|
4
4
|
|
|
5
5
|
Neither platform asks for photo library permission:
|
|
6
6
|
|
|
7
|
-
- **iOS** picks with `PHPickerViewController`, which runs out of process. `react-native-image-crop-picker` requested full photo library access first. Once a user denied it, iOS never showed the prompt again and every later `openPicker` call failed silently (OK-48227).
|
|
8
|
-
- **Android** picks with the system Photo Picker (`ActivityResultContracts.PickVisualMedia`, which falls back to `ACTION_OPEN_DOCUMENT` on devices without it)
|
|
7
|
+
- **iOS** picks with `PHPickerViewController`, which runs out of process. `react-native-image-crop-picker` requested full photo library access first. Once a user denied it, iOS never showed the prompt again and every later `openPicker` call failed silently (OK-48227). The crop gestures come from a vendored TOCropViewController 3.2.0 `TOCropView`, with the OK-51551 rotation fix that upstream still lacks.
|
|
8
|
+
- **Android** picks with the system Photo Picker (`ActivityResultContracts.PickVisualMedia`, which falls back to `ACTION_OPEN_DOCUMENT` on devices without it). The crop gestures come from uCrop `2.2.11-native`'s `UCropView`. Activity results go through the activity's `ActivityResultRegistry`, so no `ActivityEventListener` is needed.
|
|
9
|
+
|
|
10
|
+
Both platforms show the same cropper screen, drawn by this package rather than by TOCropViewController or uCrop. See [Cropper screen](#cropper-screen).
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
12
14
|
```sh
|
|
13
|
-
yarn add
|
|
15
|
+
yarn add @onekeyfe/react-native-image-crop-picker react-native-nitro-modules
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
To keep existing `react-native-image-crop-picker` imports working, install it under that name with an npm alias instead: `react-native-image-crop-picker@npm:@onekeyfe/react-native-image-crop-picker`.
|
|
17
19
|
|
|
18
20
|
- **iOS**: do not also install the `TOCropViewController` pod. This package compiles its own copy, and the duplicate classes would clash at link time.
|
|
19
21
|
- **Android**: uCrop is only published to JitPack. Add `maven { url "https://www.jitpack.io" }` to the app's `allprojects.repositories`.
|
|
@@ -23,7 +25,7 @@ The npm alias keeps existing `react-native-image-crop-picker` imports working.
|
|
|
23
25
|
```ts
|
|
24
26
|
import ImageCropPicker, {
|
|
25
27
|
ImageCropPickerError,
|
|
26
|
-
} from 'react-native-image-crop-picker';
|
|
28
|
+
} from '@onekeyfe/react-native-image-crop-picker';
|
|
27
29
|
|
|
28
30
|
try {
|
|
29
31
|
const image = await ImageCropPicker.openPicker({
|
|
@@ -50,6 +52,46 @@ const cropped = await ImageCropPicker.openCropper({
|
|
|
50
52
|
|
|
51
53
|
`openCropper` accepts `file://` URIs, absolute paths, `http(s)://` URLs, `data:` URIs and, on Android, `content://` URIs.
|
|
52
54
|
|
|
55
|
+
## Cropper screen
|
|
56
|
+
|
|
57
|
+
The cropper is a full-screen page with the same layout and metrics on iOS and Android:
|
|
58
|
+
|
|
59
|
+
- A 56 pt header with `cropperToolbarTitle` centered and a rotate button on the right. The rotate button turns the image 90° counterclockwise; `cropperRotateButtonsHidden` hides it.
|
|
60
|
+
- The crop area. The crop box keeps 20 pt from every edge, and the image outside it shows the page background at 70% opacity. The rule-of-thirds grid shows while the image is moved, unless `showCropGuidelines` is `false`.
|
|
61
|
+
- A footer with two capsule buttons, `cropperCancelText` and `cropperChooseText`, 50 pt tall with 10 pt between them. The confirm button shows a spinner while the image is saved.
|
|
62
|
+
|
|
63
|
+
`cropperAppearance` sets its colors and fonts:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
await ImageCropPicker.openPicker({
|
|
67
|
+
width: 240,
|
|
68
|
+
height: 240,
|
|
69
|
+
cropping: true,
|
|
70
|
+
cropperToolbarTitle: 'Crop image',
|
|
71
|
+
cropperAppearance: {
|
|
72
|
+
colorScheme: 'dark',
|
|
73
|
+
backgroundColor: '#0f0f0f',
|
|
74
|
+
confirmButtonColor: '#ffffffed',
|
|
75
|
+
confirmButtonTextColor: '#000000df',
|
|
76
|
+
titleFontFamily: 'Roobert-SemiBold',
|
|
77
|
+
buttonFontFamily: 'Roobert-Medium',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Field | Used for |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `colorScheme` | `'light'` or `'dark'`. Picks the fallback palette and the status bar style. Defaults to the system appearance. |
|
|
85
|
+
| `backgroundColor` | The page. The area outside the crop box is this color at 70% opacity. |
|
|
86
|
+
| `titleColor` | The title and the crop box border. |
|
|
87
|
+
| `iconColor` | The rotate button. |
|
|
88
|
+
| `cancelButtonColor`, `cancelButtonPressedColor`, `cancelButtonTextColor` | The cancel button. |
|
|
89
|
+
| `confirmButtonColor`, `confirmButtonPressedColor`, `confirmButtonTextColor` | The confirm button. |
|
|
90
|
+
| `titleFontFamily`, `buttonFontFamily` | Fonts bundled with the app. iOS looks them up with `UIFont(name:)`, Android with React Native's `ReactFontManager`. |
|
|
91
|
+
| `scale` | Multiplies every size, for apps that scale their UI. |
|
|
92
|
+
|
|
93
|
+
Colors are CSS hex strings: `#RGB`, `#RRGGBB` or `#RRGGBBAA`. Anything left out falls back to OneKey's light or dark palette.
|
|
94
|
+
|
|
53
95
|
## Behavior
|
|
54
96
|
|
|
55
97
|
- Results are always JPEG. `width` and `height` set the crop aspect ratio, and the cropped image is scaled to exactly that size, so a crop box a pixel off the ratio still yields the requested dimensions. With `freeStyleCropEnabled`, the crop keeps its own aspect ratio and is scaled to fit inside `width` × `height`.
|
|
@@ -58,6 +100,7 @@ const cropped = await ImageCropPicker.openCropper({
|
|
|
58
100
|
- Results are written to `<tmp>/react-native-image-crop-picker/` on iOS and `<cache>/react-native-image-crop-picker/` on Android. `clean()` empties that directory.
|
|
59
101
|
- Only one picker or cropper can be open at a time. A second call rejects with `E_PICKER_IN_PROGRESS`.
|
|
60
102
|
- On iOS, cancelling the cropper that `openPicker` opened returns to the photo picker. On Android it rejects with `E_PICKER_CANCELLED`.
|
|
103
|
+
- On Android the cropper keeps the calling activity's requested orientation, so a portrait-only app gets a portrait-only cropper.
|
|
61
104
|
|
|
62
105
|
## Not supported
|
|
63
106
|
|
|
@@ -19,9 +19,11 @@ Pod::Spec.new do |s|
|
|
|
19
19
|
"cpp/**/*.{hpp,cpp}",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
# Vendored TOCropViewController 3.2.0 with the OK-51551 rotation fix
|
|
23
|
-
#
|
|
24
|
-
#
|
|
22
|
+
# Vendored TOCropViewController 3.2.0 with the OK-51551 rotation fix, plus
|
|
23
|
+
# color hooks on TOCropOverlayView. Only its TOCropView is used, hosted by
|
|
24
|
+
# ImageCropperViewController. Its headers are public so the Swift sources can
|
|
25
|
+
# use it. It replaces the standalone TOCropViewController pod, which must not
|
|
26
|
+
# be installed alongside.
|
|
25
27
|
s.public_header_files = ["ios/TOCropViewController/**/*.h"]
|
|
26
28
|
# TOCropViewController looks up its strings in a bundle with exactly this name.
|
|
27
29
|
s.resource_bundles = {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
<application>
|
|
5
5
|
<activity
|
|
6
|
-
android:name="com.
|
|
6
|
+
android:name="com.margelo.nitro.reactnativeimagecroppicker.ImageCropperActivity"
|
|
7
7
|
android:exported="false"
|
|
8
|
-
android:theme="@style/Theme.AppCompat.
|
|
8
|
+
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
|
9
9
|
|
|
10
10
|
<!-- Prompts Google Play services to install the backported photo picker module. -->
|
|
11
11
|
<service
|
|
@@ -28,16 +28,11 @@ internal data class ImageCropPickerConfig(
|
|
|
28
28
|
val freeStyleCropEnabled: Boolean,
|
|
29
29
|
val cropperCircleOverlay: Boolean,
|
|
30
30
|
val cropperToolbarTitle: String?,
|
|
31
|
-
val
|
|
32
|
-
val
|
|
33
|
-
val
|
|
34
|
-
val cropperStatusBarLight: Boolean,
|
|
35
|
-
val cropperNavigationBarLight: Boolean,
|
|
31
|
+
val cropperChooseText: String?,
|
|
32
|
+
val cropperCancelText: String?,
|
|
33
|
+
val cropperRotateButtonsHidden: Boolean,
|
|
36
34
|
val showCropGuidelines: Boolean,
|
|
37
|
-
val
|
|
38
|
-
val enableRotationGesture: Boolean,
|
|
39
|
-
val hideBottomControls: Boolean,
|
|
40
|
-
val disableCropperColorSetters: Boolean,
|
|
35
|
+
val appearance: ImageCropperAppearance?,
|
|
41
36
|
) {
|
|
42
37
|
companion object {
|
|
43
38
|
fun from(options: ImageCropPickerOptions, forceCropping: Boolean) = ImageCropPickerConfig(
|
|
@@ -51,16 +46,11 @@ internal data class ImageCropPickerConfig(
|
|
|
51
46
|
freeStyleCropEnabled = options.freeStyleCropEnabled == true,
|
|
52
47
|
cropperCircleOverlay = options.cropperCircleOverlay == true,
|
|
53
48
|
cropperToolbarTitle = options.cropperToolbarTitle,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
cropperStatusBarLight = options.cropperStatusBarLight ?: true,
|
|
58
|
-
cropperNavigationBarLight = options.cropperNavigationBarLight ?: false,
|
|
49
|
+
cropperChooseText = options.cropperChooseText,
|
|
50
|
+
cropperCancelText = options.cropperCancelText,
|
|
51
|
+
cropperRotateButtonsHidden = options.cropperRotateButtonsHidden == true,
|
|
59
52
|
showCropGuidelines = options.showCropGuidelines ?: true,
|
|
60
|
-
|
|
61
|
-
enableRotationGesture = options.enableRotationGesture == true,
|
|
62
|
-
hideBottomControls = options.hideBottomControls == true,
|
|
63
|
-
disableCropperColorSetters = options.disableCropperColorSetters == true,
|
|
53
|
+
appearance = options.cropperAppearance,
|
|
64
54
|
)
|
|
65
55
|
}
|
|
66
56
|
}
|
package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerSession.kt
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
package com.margelo.nitro.reactnativeimagecroppicker
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
|
-
import android.
|
|
5
|
-
import android.graphics.Color
|
|
4
|
+
import android.content.res.Configuration
|
|
6
5
|
import android.net.Uri
|
|
7
6
|
import android.os.Handler
|
|
8
7
|
import android.os.Looper
|
|
@@ -15,8 +14,6 @@ import androidx.activity.result.contract.ActivityResultContract
|
|
|
15
14
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
16
15
|
import com.margelo.nitro.core.Promise
|
|
17
16
|
import com.margelo.nitro.nativelogger.OneKeyLog
|
|
18
|
-
import com.yalantis.ucrop.UCrop
|
|
19
|
-
import com.yalantis.ucrop.UCropActivity
|
|
20
17
|
import java.io.File
|
|
21
18
|
import java.util.UUID
|
|
22
19
|
import java.util.concurrent.ExecutorService
|
|
@@ -124,30 +121,37 @@ internal class ImageCropPickerSession(
|
|
|
124
121
|
}
|
|
125
122
|
temporaryFiles.add(destination)
|
|
126
123
|
|
|
127
|
-
val
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
val systemIsDark = (activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
|
125
|
+
Configuration.UI_MODE_NIGHT_YES
|
|
126
|
+
val intent = ImageCropperActivity.createIntent(
|
|
127
|
+
activity,
|
|
128
|
+
source,
|
|
129
|
+
Uri.fromFile(destination),
|
|
130
|
+
config,
|
|
131
|
+
ImageCropperTheme.resolve(config.appearance, systemIsDark),
|
|
132
|
+
// Keeps the cropper in the app's orientation, e.g. portrait on phones.
|
|
133
|
+
activity.requestedOrientation,
|
|
134
|
+
)
|
|
131
135
|
val launcher = register(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
132
136
|
handleCropResult(result)
|
|
133
137
|
}
|
|
134
|
-
launch { launcher.launch(
|
|
138
|
+
launch { launcher.launch(intent) }
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
private fun handleCropResult(result: ActivityResult) {
|
|
138
142
|
val data = result.data
|
|
139
143
|
when (result.resultCode) {
|
|
140
144
|
Activity.RESULT_OK -> {
|
|
141
|
-
val output = data?.let {
|
|
145
|
+
val output = data?.let { ImageCropperActivity.getOutputUri(it) }
|
|
142
146
|
if (data == null || output == null) {
|
|
143
147
|
finish(Result.failure(ImageCropPickerException.noImageData()))
|
|
144
148
|
return
|
|
145
149
|
}
|
|
146
150
|
val cropRect = CropRect(
|
|
147
|
-
x = data.getIntExtra(
|
|
148
|
-
y = data.getIntExtra(
|
|
149
|
-
width = data.getIntExtra(
|
|
150
|
-
height = data.getIntExtra(
|
|
151
|
+
x = data.getIntExtra(ImageCropperActivity.EXTRA_OUTPUT_OFFSET_X, -1).toDouble(),
|
|
152
|
+
y = data.getIntExtra(ImageCropperActivity.EXTRA_OUTPUT_OFFSET_Y, -1).toDouble(),
|
|
153
|
+
width = data.getIntExtra(ImageCropperActivity.EXTRA_OUTPUT_WIDTH, -1).toDouble(),
|
|
154
|
+
height = data.getIntExtra(ImageCropperActivity.EXTRA_OUTPUT_HEIGHT, -1).toDouble(),
|
|
151
155
|
)
|
|
152
156
|
val filename = pickedFilename
|
|
153
157
|
runInBackground(
|
|
@@ -164,35 +168,15 @@ internal class ImageCropPickerSession(
|
|
|
164
168
|
onComplete = ::finish,
|
|
165
169
|
)
|
|
166
170
|
}
|
|
167
|
-
|
|
168
|
-
val message = data?.
|
|
171
|
+
ImageCropperActivity.RESULT_ERROR -> {
|
|
172
|
+
val message = data?.getStringExtra(ImageCropperActivity.EXTRA_ERROR_MESSAGE)
|
|
173
|
+
?: "Cannot crop image"
|
|
169
174
|
finish(Result.failure(ImageCropPickerException.noImageData(message)))
|
|
170
175
|
}
|
|
171
176
|
else -> finish(Result.failure(ImageCropPickerException.cancelled()))
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
private fun buildCropOptions(): UCrop.Options = UCrop.Options().apply {
|
|
176
|
-
setCompressionFormat(Bitmap.CompressFormat.JPEG)
|
|
177
|
-
setCompressionQuality(100)
|
|
178
|
-
setCircleDimmedLayer(config.cropperCircleOverlay)
|
|
179
|
-
setFreeStyleCropEnabled(config.freeStyleCropEnabled)
|
|
180
|
-
setShowCropGrid(config.showCropGuidelines)
|
|
181
|
-
setShowCropFrame(config.showCropFrame)
|
|
182
|
-
setHideBottomControls(config.hideBottomControls)
|
|
183
|
-
config.cropperToolbarTitle?.let { setToolbarTitle(it) }
|
|
184
|
-
if (config.enableRotationGesture) {
|
|
185
|
-
setAllowedGestures(UCropActivity.ALL, UCropActivity.ALL, UCropActivity.ALL)
|
|
186
|
-
}
|
|
187
|
-
if (!config.disableCropperColorSetters) {
|
|
188
|
-
parseColor(config.cropperActiveWidgetColor)?.let { setActiveControlsWidgetColor(it) }
|
|
189
|
-
parseColor(config.cropperToolbarColor)?.let { setToolbarColor(it) }
|
|
190
|
-
parseColor(config.cropperToolbarWidgetColor)?.let { setToolbarWidgetColor(it) }
|
|
191
|
-
setStatusBarLight(config.cropperStatusBarLight)
|
|
192
|
-
setNavigationBarLight(config.cropperNavigationBarLight)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
180
|
private fun <I, O> register(
|
|
197
181
|
contract: ActivityResultContract<I, O>,
|
|
198
182
|
callback: (O) -> Unit,
|
|
@@ -280,15 +264,6 @@ internal class ImageCropPickerSession(
|
|
|
280
264
|
return displayName ?: uri.lastPathSegment
|
|
281
265
|
}
|
|
282
266
|
|
|
283
|
-
private fun parseColor(value: String?): Int? =
|
|
284
|
-
value?.let {
|
|
285
|
-
try {
|
|
286
|
-
Color.parseColor(it)
|
|
287
|
-
} catch (error: IllegalArgumentException) {
|
|
288
|
-
null
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
267
|
companion object {
|
|
293
268
|
private const val TAG = "ImageCropPicker"
|
|
294
269
|
private val mainHandler = Handler(Looper.getMainLooper())
|