@onekeyfe/react-native-image-crop-picker 3.0.162 → 3.0.165
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 +18 -2
- package/ReactNativeImageCropPicker.podspec +6 -1
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerException.kt +5 -4
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerImageProcessor.kt +53 -2
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerSession.kt +5 -5
- package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ReactNativeImageCropPicker.kt +4 -1
- package/ios/ImageCropPickerError.swift +1 -0
- package/ios/ImageCropPickerImageProcessor.swift +42 -0
- package/ios/ImageCropPickerSession.swift +19 -0
- package/ios/tests/ImageOriginalCopyTests.swift +55 -0
- package/lib/module/index.js +1 -1
- package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts +1 -0
- package/lib/typescript/src/ReactNativeImageCropPicker.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JImageCropPickerOptions.hpp +5 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerOptions.kt +7 -2
- package/nitrogen/generated/ios/swift/ImageCropPickerOptions.swift +19 -1
- package/nitrogen/generated/shared/c++/ImageCropPickerOptions.hpp +5 -1
- package/package.json +18 -5
- package/src/ReactNativeImageCropPicker.nitro.ts +2 -0
- package/src/index.tsx +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Single photo picker and cropper for OneKey, built on [Nitro Modules](https://nitro.margelo.com/). It replaces `react-native-image-crop-picker` and keeps the part of its API that OneKey uses: `openPicker`, `openCropper`, `clean`, `cleanSingle`, and the `E_*` error codes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Picking never asks for photo library permission:
|
|
6
6
|
|
|
7
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
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
9
|
|
|
10
10
|
Both platforms show the same cropper screen, drawn by this package rather than by TOCropViewController or uCrop. See [Cropper screen](#cropper-screen).
|
|
11
11
|
|
|
12
|
+
The behavioral contract and pending acceptance cases are in [docs/SPEC.md](docs/SPEC.md).
|
|
13
|
+
|
|
14
|
+
## Original selection
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
const image = await ImageCropPicker.openPicker({ preserveOriginal: true });
|
|
18
|
+
// Read the selected file before cleaning it.
|
|
19
|
+
await ImageCropPicker.cleanSingle(image.path);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Original selection preserves the selected representation without JPEG conversion.
|
|
23
|
+
Compression options are ignored unless cropping is enabled. Originals are limited
|
|
24
|
+
to 64 MiB. Saving and its authorization are provided by the separate
|
|
25
|
+
`@onekeyfe/react-native-photo-library`; this picker exposes neither saving nor
|
|
26
|
+
photo-library permission APIs.
|
|
27
|
+
|
|
12
28
|
## Installation
|
|
13
29
|
|
|
14
30
|
```sh
|
|
@@ -94,7 +110,7 @@ Colors are CSS hex strings: `#RGB`, `#RRGGBB` or `#RRGGBBAA`. Anything left out
|
|
|
94
110
|
|
|
95
111
|
## Behavior
|
|
96
112
|
|
|
97
|
-
-
|
|
113
|
+
- Processed 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`.
|
|
98
114
|
- `compressImageMaxWidth`, `compressImageMaxHeight` and `compressImageQuality` apply after cropping. The default quality is 0.8 on iOS and 1 on Android, as in `react-native-image-crop-picker`.
|
|
99
115
|
- Photos are decoded at most 4096 px on the long side, which keeps very large photos from exhausting memory. `cropRect` is reported in the original image's coordinates on iOS.
|
|
100
116
|
- 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.
|
|
@@ -25,11 +25,12 @@ Pod::Spec.new do |s|
|
|
|
25
25
|
# use it. It replaces the standalone TOCropViewController pod, which must not
|
|
26
26
|
# be installed alongside.
|
|
27
27
|
s.public_header_files = ["ios/TOCropViewController/**/*.h"]
|
|
28
|
+
s.exclude_files = "ios/tests/**/*"
|
|
28
29
|
# TOCropViewController looks up its strings in a bundle with exactly this name.
|
|
29
30
|
s.resource_bundles = {
|
|
30
31
|
"TOCropViewControllerBundle" => ["ios/TOCropViewController/Resources/**/*.{lproj,xcprivacy}"],
|
|
31
32
|
}
|
|
32
|
-
s.frameworks = "PhotosUI", "UniformTypeIdentifiers"
|
|
33
|
+
s.frameworks = "Photos", "PhotosUI", "UniformTypeIdentifiers"
|
|
33
34
|
|
|
34
35
|
s.dependency 'React-jsi'
|
|
35
36
|
s.dependency 'React-callinvoker'
|
|
@@ -39,4 +40,8 @@ Pod::Spec.new do |s|
|
|
|
39
40
|
add_nitrogen_files(s)
|
|
40
41
|
|
|
41
42
|
install_modules_dependencies(s)
|
|
43
|
+
|
|
44
|
+
s.test_spec "Tests" do |test_spec|
|
|
45
|
+
test_spec.source_files = "ios/tests/**/*.swift"
|
|
46
|
+
end
|
|
42
47
|
end
|
|
@@ -26,10 +26,10 @@ internal class ImageCropPickerException(
|
|
|
26
26
|
fun noActivity() = ImageCropPickerException(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist")
|
|
27
27
|
|
|
28
28
|
fun failedToShowPicker(cause: Throwable) =
|
|
29
|
-
ImageCropPickerException(E_FAILED_TO_SHOW_PICKER,
|
|
29
|
+
ImageCropPickerException(E_FAILED_TO_SHOW_PICKER, "Cannot show picker", cause)
|
|
30
30
|
|
|
31
|
-
fun noImageData(
|
|
32
|
-
ImageCropPickerException(E_NO_IMAGE_DATA_FOUND,
|
|
31
|
+
fun noImageData() =
|
|
32
|
+
ImageCropPickerException(E_NO_IMAGE_DATA_FOUND, "Cannot find image data")
|
|
33
33
|
|
|
34
34
|
fun cropperImageNotFound() =
|
|
35
35
|
ImageCropPickerException(E_CROPPER_IMAGE_NOT_FOUND, "Can't find the image at the specified path")
|
|
@@ -38,8 +38,9 @@ internal class ImageCropPickerException(
|
|
|
38
38
|
ImageCropPickerException(E_CANNOT_SAVE_IMAGE, "Cannot save image. Unable to write to tmp location.", cause)
|
|
39
39
|
|
|
40
40
|
fun lowMemory(cause: Throwable) =
|
|
41
|
-
ImageCropPickerException(E_LOW_MEMORY_ERROR,
|
|
41
|
+
ImageCropPickerException(E_LOW_MEMORY_ERROR, "Out of memory", cause)
|
|
42
42
|
|
|
43
43
|
fun cleanupFailed() = ImageCropPickerException(E_ERROR_WHILE_CLEANING_FILES, "Error while cleaning up tmp files")
|
|
44
|
+
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -6,6 +6,7 @@ import android.graphics.BitmapFactory
|
|
|
6
6
|
import android.graphics.Matrix
|
|
7
7
|
import android.net.Uri
|
|
8
8
|
import android.util.Base64
|
|
9
|
+
import android.webkit.MimeTypeMap
|
|
9
10
|
import androidx.exifinterface.media.ExifInterface
|
|
10
11
|
import java.io.File
|
|
11
12
|
import java.io.FileOutputStream
|
|
@@ -22,6 +23,7 @@ internal data class ImageCropPickerConfig(
|
|
|
22
23
|
val height: Int?,
|
|
23
24
|
val cropping: Boolean,
|
|
24
25
|
val includeBase64: Boolean,
|
|
26
|
+
val preserveOriginal: Boolean,
|
|
25
27
|
val compressImageQuality: Double?,
|
|
26
28
|
val compressImageMaxWidth: Double?,
|
|
27
29
|
val compressImageMaxHeight: Double?,
|
|
@@ -40,6 +42,7 @@ internal data class ImageCropPickerConfig(
|
|
|
40
42
|
height = options.height?.takeIf { it.isFinite() }?.roundToInt()?.takeIf { it > 0 },
|
|
41
43
|
cropping = forceCropping || options.cropping == true,
|
|
42
44
|
includeBase64 = options.includeBase64 == true,
|
|
45
|
+
preserveOriginal = options.preserveOriginal == true,
|
|
43
46
|
compressImageQuality = options.compressImageQuality,
|
|
44
47
|
compressImageMaxWidth = options.compressImageMaxWidth,
|
|
45
48
|
compressImageMaxHeight = options.compressImageMaxHeight,
|
|
@@ -62,6 +65,54 @@ internal object ImageCropPickerImageProcessor {
|
|
|
62
65
|
// Matches react-native-image-crop-picker on Android.
|
|
63
66
|
private const val DEFAULT_COMPRESS_QUALITY = 1.0
|
|
64
67
|
private const val TEMPORARY_DIRECTORY_NAME = "react-native-image-crop-picker"
|
|
68
|
+
const val MAX_ORIGINAL_BYTES = 64L * 1024 * 1024
|
|
69
|
+
|
|
70
|
+
fun imageBounds(file: File): BitmapFactory.Options {
|
|
71
|
+
if (!file.isFile || file.length() <= 0 || file.length() > MAX_ORIGINAL_BYTES) {
|
|
72
|
+
throw ImageCropPickerException.noImageData()
|
|
73
|
+
}
|
|
74
|
+
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
75
|
+
BitmapFactory.decodeFile(file.absolutePath, bounds)
|
|
76
|
+
if (bounds.outWidth <= 0 || bounds.outHeight <= 0 || bounds.outMimeType?.startsWith("image/") != true) {
|
|
77
|
+
throw ImageCropPickerException.noImageData()
|
|
78
|
+
}
|
|
79
|
+
return bounds
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fun copyOriginal(context: Context, uri: Uri, config: ImageCropPickerConfig, filename: String?): PickedImage {
|
|
83
|
+
val mime = context.contentResolver.getType(uri)
|
|
84
|
+
val extension = mime?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: "img"
|
|
85
|
+
val file = createTemporaryFile(context, extension)
|
|
86
|
+
try {
|
|
87
|
+
openStream(context, uri).use { input ->
|
|
88
|
+
FileOutputStream(file).use { output ->
|
|
89
|
+
val buffer = ByteArray(8192)
|
|
90
|
+
var total = 0L
|
|
91
|
+
while (true) {
|
|
92
|
+
val count = input.read(buffer)
|
|
93
|
+
if (count < 0) break
|
|
94
|
+
total += count
|
|
95
|
+
if (total > MAX_ORIGINAL_BYTES) throw ImageCropPickerException.noImageData()
|
|
96
|
+
output.write(buffer, 0, count)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
val bounds = imageBounds(file)
|
|
101
|
+
return PickedImage(
|
|
102
|
+
path = Uri.fromFile(file).toString(),
|
|
103
|
+
size = file.length().toDouble(),
|
|
104
|
+
width = bounds.outWidth.toDouble(),
|
|
105
|
+
height = bounds.outHeight.toDouble(),
|
|
106
|
+
mime = bounds.outMimeType,
|
|
107
|
+
data = if (config.includeBase64) Base64.encodeToString(file.readBytes(), Base64.NO_WRAP) else null,
|
|
108
|
+
cropRect = null,
|
|
109
|
+
filename = filename,
|
|
110
|
+
)
|
|
111
|
+
} catch (error: Throwable) {
|
|
112
|
+
file.delete()
|
|
113
|
+
throw error
|
|
114
|
+
}
|
|
115
|
+
}
|
|
65
116
|
|
|
66
117
|
fun temporaryDirectory(context: Context): File {
|
|
67
118
|
val directory = File(context.cacheDir, TEMPORARY_DIRECTORY_NAME)
|
|
@@ -135,7 +186,7 @@ internal object ImageCropPickerImageProcessor {
|
|
|
135
186
|
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
136
187
|
openStream(context, uri).use { BitmapFactory.decodeStream(it, null, bounds) }
|
|
137
188
|
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) {
|
|
138
|
-
throw ImageCropPickerException.noImageData(
|
|
189
|
+
throw ImageCropPickerException.noImageData()
|
|
139
190
|
}
|
|
140
191
|
|
|
141
192
|
var sampleSize = 1
|
|
@@ -147,7 +198,7 @@ internal object ImageCropPickerImageProcessor {
|
|
|
147
198
|
openStream(context, uri).use { BitmapFactory.decodeStream(it, null, options) }
|
|
148
199
|
} catch (error: OutOfMemoryError) {
|
|
149
200
|
throw ImageCropPickerException.lowMemory(error)
|
|
150
|
-
} ?: throw ImageCropPickerException.noImageData(
|
|
201
|
+
} ?: throw ImageCropPickerException.noImageData()
|
|
151
202
|
|
|
152
203
|
val orientation = try {
|
|
153
204
|
openStream(context, uri).use {
|
package/android/src/main/java/com/margelo/nitro/reactnativeimagecroppicker/ImageCropPickerSession.kt
CHANGED
|
@@ -85,7 +85,9 @@ internal class ImageCropPickerSession(
|
|
|
85
85
|
val filename = queryDisplayName(uri)
|
|
86
86
|
runInBackground(
|
|
87
87
|
work = {
|
|
88
|
-
|
|
88
|
+
if (config.preserveOriginal) {
|
|
89
|
+
ImageCropPickerImageProcessor.copyOriginal(activity, uri, config, filename)
|
|
90
|
+
} else ImageCropPickerImageProcessor.makeResult(
|
|
89
91
|
activity,
|
|
90
92
|
ImageCropPickerImageProcessor.decodeBitmap(activity, uri),
|
|
91
93
|
config,
|
|
@@ -169,9 +171,7 @@ internal class ImageCropPickerSession(
|
|
|
169
171
|
)
|
|
170
172
|
}
|
|
171
173
|
ImageCropperActivity.RESULT_ERROR -> {
|
|
172
|
-
|
|
173
|
-
?: "Cannot crop image"
|
|
174
|
-
finish(Result.failure(ImageCropPickerException.noImageData(message)))
|
|
174
|
+
finish(Result.failure(ImageCropPickerException.noImageData()))
|
|
175
175
|
}
|
|
176
176
|
else -> finish(Result.failure(ImageCropPickerException.cancelled()))
|
|
177
177
|
}
|
|
@@ -212,7 +212,7 @@ internal class ImageCropPickerSession(
|
|
|
212
212
|
} catch (error: OutOfMemoryError) {
|
|
213
213
|
Result.failure(ImageCropPickerException.lowMemory(error))
|
|
214
214
|
} catch (error: Exception) {
|
|
215
|
-
Result.failure(ImageCropPickerException.noImageData(
|
|
215
|
+
Result.failure(ImageCropPickerException.noImageData())
|
|
216
216
|
}
|
|
217
217
|
mainHandler.post {
|
|
218
218
|
isProcessing = false
|
|
@@ -3,6 +3,7 @@ package com.margelo.nitro.reactnativeimagecroppicker
|
|
|
3
3
|
import android.os.Handler
|
|
4
4
|
import android.os.Looper
|
|
5
5
|
import androidx.activity.ComponentActivity
|
|
6
|
+
import com.facebook.react.ReactApplication
|
|
6
7
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
7
8
|
import com.margelo.nitro.NitroModules
|
|
8
9
|
import com.margelo.nitro.core.Promise
|
|
@@ -50,7 +51,9 @@ class ReactNativeImageCropPicker : HybridReactNativeImageCropPickerSpec() {
|
|
|
50
51
|
session.abandon()
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
val
|
|
54
|
+
val context = NitroModules.applicationContext
|
|
55
|
+
val activity = ((context?.applicationContext as? ReactApplication)?.reactHost?.currentReactContext?.currentActivity
|
|
56
|
+
?: context?.currentActivity) as? ComponentActivity
|
|
54
57
|
if (activity == null || activity.isFinishing || activity.isDestroyed) {
|
|
55
58
|
promise.reject(ImageCropPickerException.noActivity())
|
|
56
59
|
return@post
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import ImageIO
|
|
3
3
|
import UIKit
|
|
4
|
+
import UniformTypeIdentifiers
|
|
4
5
|
|
|
5
6
|
// Plain Swift copy of the options, so no C++ backed struct outlives the JS call.
|
|
6
7
|
struct ImageCropPickerConfig {
|
|
@@ -8,6 +9,7 @@ struct ImageCropPickerConfig {
|
|
|
8
9
|
let height: Double?
|
|
9
10
|
let cropping: Bool
|
|
10
11
|
let includeBase64: Bool
|
|
12
|
+
let preserveOriginal: Bool
|
|
11
13
|
let compressImageQuality: Double?
|
|
12
14
|
let compressImageMaxWidth: Double?
|
|
13
15
|
let compressImageMaxHeight: Double?
|
|
@@ -25,6 +27,7 @@ struct ImageCropPickerConfig {
|
|
|
25
27
|
height = options.height
|
|
26
28
|
cropping = forceCropping || (options.cropping ?? false)
|
|
27
29
|
includeBase64 = options.includeBase64 ?? false
|
|
30
|
+
preserveOriginal = options.preserveOriginal ?? false
|
|
28
31
|
compressImageQuality = options.compressImageQuality
|
|
29
32
|
compressImageMaxWidth = options.compressImageMaxWidth
|
|
30
33
|
compressImageMaxHeight = options.compressImageMaxHeight
|
|
@@ -58,6 +61,45 @@ enum ImageCropPickerImageProcessor {
|
|
|
58
61
|
static let maxDecodedPixelSize = 4096
|
|
59
62
|
static let defaultCompressQuality = 0.8
|
|
60
63
|
static let temporaryDirectoryName = "react-native-image-crop-picker"
|
|
64
|
+
static let maxOriginalBytes = 64 * 1024 * 1024
|
|
65
|
+
|
|
66
|
+
static func imageMetadata(at url: URL) throws -> (width: Int, height: Int, type: UTType, size: Int) {
|
|
67
|
+
let size = (try? url.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0
|
|
68
|
+
guard size > 0, size <= maxOriginalBytes,
|
|
69
|
+
let source = CGImageSourceCreateWithURL(url as CFURL, [kCGImageSourceShouldCache: false] as CFDictionary),
|
|
70
|
+
let identifier = CGImageSourceGetType(source),
|
|
71
|
+
let type = UTType(identifier as String), type.conforms(to: .image),
|
|
72
|
+
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any],
|
|
73
|
+
let width = (properties[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
|
|
74
|
+
let height = (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue,
|
|
75
|
+
width > 0, height > 0 else {
|
|
76
|
+
throw ImageCropPickerError.noImageData
|
|
77
|
+
}
|
|
78
|
+
return (width, height, type, size)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static func copyOriginal(at sourceURL: URL, config: ImageCropPickerConfig, filename: String?) throws -> PickedImage {
|
|
82
|
+
let metadata = try imageMetadata(at: sourceURL)
|
|
83
|
+
let destination = try temporaryDirectory()
|
|
84
|
+
.appendingPathComponent(UUID().uuidString)
|
|
85
|
+
.appendingPathExtension(metadata.type.preferredFilenameExtension ?? "img")
|
|
86
|
+
do {
|
|
87
|
+
try FileManager.default.copyItem(at: sourceURL, to: destination)
|
|
88
|
+
return PickedImage(
|
|
89
|
+
path: destination.absoluteString,
|
|
90
|
+
size: Double(metadata.size),
|
|
91
|
+
width: Double(metadata.width),
|
|
92
|
+
height: Double(metadata.height),
|
|
93
|
+
mime: metadata.type.preferredMIMEType ?? "application/octet-stream",
|
|
94
|
+
data: config.includeBase64 ? try Data(contentsOf: destination).base64EncodedString() : nil,
|
|
95
|
+
cropRect: nil,
|
|
96
|
+
filename: filename
|
|
97
|
+
)
|
|
98
|
+
} catch {
|
|
99
|
+
try? FileManager.default.removeItem(at: destination)
|
|
100
|
+
throw ImageCropPickerError.cannotSaveImage
|
|
101
|
+
}
|
|
102
|
+
}
|
|
61
103
|
|
|
62
104
|
static func decodeImage(at url: URL) -> DecodedImage? {
|
|
63
105
|
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
|
|
@@ -136,6 +136,17 @@ final class ImageCropPickerSession: NSObject {
|
|
|
136
136
|
showLoading(in: picker.view)
|
|
137
137
|
|
|
138
138
|
provider.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { [self] url, _ in
|
|
139
|
+
if config.preserveOriginal && !config.cropping {
|
|
140
|
+
guard let url else {
|
|
141
|
+
completeOriginal(.failure(ImageCropPickerError.noImageData))
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
// The provider deletes its file when this callback returns.
|
|
145
|
+
completeOriginal(Result {
|
|
146
|
+
try ImageCropPickerImageProcessor.copyOriginal(at: url, config: config, filename: filename)
|
|
147
|
+
})
|
|
148
|
+
return
|
|
149
|
+
}
|
|
139
150
|
// The file is removed once this handler returns, so decode it right here.
|
|
140
151
|
if let url, let decoded = ImageCropPickerImageProcessor.decodeImage(at: url) {
|
|
141
152
|
handlePickedImage(decoded, filename: filename)
|
|
@@ -150,6 +161,14 @@ final class ImageCropPickerSession: NSObject {
|
|
|
150
161
|
}
|
|
151
162
|
}
|
|
152
163
|
|
|
164
|
+
private func completeOriginal(_ result: Result<PickedImage, Error>) {
|
|
165
|
+
DispatchQueue.main.async { [self] in
|
|
166
|
+
isBusy = false
|
|
167
|
+
hideLoading()
|
|
168
|
+
dismissAll { self.finish(result) }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
153
172
|
// Runs on the item provider's background queue.
|
|
154
173
|
private func handlePickedImage(_ decoded: DecodedImage?, filename: String?) {
|
|
155
174
|
guard let decoded else {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
@testable import ReactNativeImageCropPicker
|
|
5
|
+
|
|
6
|
+
final class ImageOriginalCopyTests: XCTestCase {
|
|
7
|
+
func testOriginalCopyPreservesPNGBytesAndDimensions() throws {
|
|
8
|
+
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 12, height: 8), format: {
|
|
9
|
+
let format = UIGraphicsImageRendererFormat()
|
|
10
|
+
format.scale = 1
|
|
11
|
+
return format
|
|
12
|
+
}())
|
|
13
|
+
let bytes = renderer.pngData { context in
|
|
14
|
+
UIColor.red.setFill()
|
|
15
|
+
context.fill(CGRect(x: 0, y: 0, width: 12, height: 8))
|
|
16
|
+
}
|
|
17
|
+
let source = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".png")
|
|
18
|
+
try bytes.write(to: source)
|
|
19
|
+
defer { try? FileManager.default.removeItem(at: source) }
|
|
20
|
+
let options: ImageCropPickerOptions = .init(
|
|
21
|
+
width: nil, height: nil, cropping: false, includeBase64: true, preserveOriginal: true,
|
|
22
|
+
compressImageQuality: 0.1, compressImageMaxWidth: 1, compressImageMaxHeight: 1,
|
|
23
|
+
freeStyleCropEnabled: nil, cropperCircleOverlay: nil, cropperToolbarTitle: nil,
|
|
24
|
+
cropperChooseText: nil, cropperCancelText: nil, cropperRotateButtonsHidden: nil,
|
|
25
|
+
showCropGuidelines: nil, cropperAppearance: nil
|
|
26
|
+
)
|
|
27
|
+
let config = ImageCropPickerConfig(options)
|
|
28
|
+
let result = try ImageCropPickerImageProcessor.copyOriginal(at: source, config: config, filename: "source.png")
|
|
29
|
+
defer { try? ImageCropPickerImageProcessor.removeFile(atPath: result.path) }
|
|
30
|
+
let copied = try XCTUnwrap(URL(string: result.path))
|
|
31
|
+
XCTAssertEqual(try Data(contentsOf: copied), bytes)
|
|
32
|
+
XCTAssertEqual(result.data, bytes.base64EncodedString())
|
|
33
|
+
XCTAssertEqual(result.mime, "image/png")
|
|
34
|
+
XCTAssertEqual(result.width, 12)
|
|
35
|
+
XCTAssertEqual(result.height, 8)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func testMetadataRejectsNonImageAndOversizedFile() throws {
|
|
39
|
+
let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
40
|
+
defer { try? FileManager.default.removeItem(at: file) }
|
|
41
|
+
try Data("not an image".utf8).write(to: file)
|
|
42
|
+
XCTAssertThrowsError(try ImageCropPickerImageProcessor.imageMetadata(at: file))
|
|
43
|
+
let handle = try FileHandle(forWritingTo: file)
|
|
44
|
+
try handle.truncate(atOffset: UInt64(ImageCropPickerImageProcessor.maxOriginalBytes + 1))
|
|
45
|
+
try handle.close()
|
|
46
|
+
XCTAssertThrowsError(try ImageCropPickerImageProcessor.imageMetadata(at: file))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func testMissingSourceUsesTheDocumentedError() {
|
|
50
|
+
let missing = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
51
|
+
XCTAssertThrowsError(try ImageCropPickerImageProcessor.imageMetadata(at: missing)) { error in
|
|
52
|
+
XCTAssertEqual((error as? ImageCropPickerError)?.code, ImageCropPickerError.noImageData.code)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/lib/module/index.js
CHANGED
|
@@ -40,7 +40,7 @@ function toImageCropPickerError(error) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Options accepted for source compatibility with react-native-image-crop-picker.
|
|
43
|
-
// Only single photos are supported
|
|
43
|
+
// Only single photos are supported; processed results are JPEG.
|
|
44
44
|
|
|
45
45
|
export async function openPicker(options = {}) {
|
|
46
46
|
try {
|
|
@@ -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,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;
|
|
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;IAExB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,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"}
|
|
@@ -44,6 +44,8 @@ namespace margelo::nitro::reactnativeimagecroppicker {
|
|
|
44
44
|
jni::local_ref<jni::JBoolean> cropping = this->getFieldValue(fieldCropping);
|
|
45
45
|
static const auto fieldIncludeBase64 = clazz->getField<jni::JBoolean>("includeBase64");
|
|
46
46
|
jni::local_ref<jni::JBoolean> includeBase64 = this->getFieldValue(fieldIncludeBase64);
|
|
47
|
+
static const auto fieldPreserveOriginal = clazz->getField<jni::JBoolean>("preserveOriginal");
|
|
48
|
+
jni::local_ref<jni::JBoolean> preserveOriginal = this->getFieldValue(fieldPreserveOriginal);
|
|
47
49
|
static const auto fieldCompressImageQuality = clazz->getField<jni::JDouble>("compressImageQuality");
|
|
48
50
|
jni::local_ref<jni::JDouble> compressImageQuality = this->getFieldValue(fieldCompressImageQuality);
|
|
49
51
|
static const auto fieldCompressImageMaxWidth = clazz->getField<jni::JDouble>("compressImageMaxWidth");
|
|
@@ -71,6 +73,7 @@ namespace margelo::nitro::reactnativeimagecroppicker {
|
|
|
71
73
|
height != nullptr ? std::make_optional(height->value()) : std::nullopt,
|
|
72
74
|
cropping != nullptr ? std::make_optional(static_cast<bool>(cropping->value())) : std::nullopt,
|
|
73
75
|
includeBase64 != nullptr ? std::make_optional(static_cast<bool>(includeBase64->value())) : std::nullopt,
|
|
76
|
+
preserveOriginal != nullptr ? std::make_optional(static_cast<bool>(preserveOriginal->value())) : std::nullopt,
|
|
74
77
|
compressImageQuality != nullptr ? std::make_optional(compressImageQuality->value()) : std::nullopt,
|
|
75
78
|
compressImageMaxWidth != nullptr ? std::make_optional(compressImageMaxWidth->value()) : std::nullopt,
|
|
76
79
|
compressImageMaxHeight != nullptr ? std::make_optional(compressImageMaxHeight->value()) : std::nullopt,
|
|
@@ -91,7 +94,7 @@ namespace margelo::nitro::reactnativeimagecroppicker {
|
|
|
91
94
|
*/
|
|
92
95
|
[[maybe_unused]]
|
|
93
96
|
static jni::local_ref<JImageCropPickerOptions::javaobject> fromCpp(const ImageCropPickerOptions& value) {
|
|
94
|
-
using JSignature = JImageCropPickerOptions(jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<JImageCropperAppearance>);
|
|
97
|
+
using JSignature = JImageCropPickerOptions(jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JString>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<JImageCropperAppearance>);
|
|
95
98
|
static const auto clazz = javaClassStatic();
|
|
96
99
|
static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
|
|
97
100
|
return create(
|
|
@@ -100,6 +103,7 @@ namespace margelo::nitro::reactnativeimagecroppicker {
|
|
|
100
103
|
value.height.has_value() ? jni::JDouble::valueOf(value.height.value()) : nullptr,
|
|
101
104
|
value.cropping.has_value() ? jni::JBoolean::valueOf(value.cropping.value()) : nullptr,
|
|
102
105
|
value.includeBase64.has_value() ? jni::JBoolean::valueOf(value.includeBase64.value()) : nullptr,
|
|
106
|
+
value.preserveOriginal.has_value() ? jni::JBoolean::valueOf(value.preserveOriginal.value()) : nullptr,
|
|
103
107
|
value.compressImageQuality.has_value() ? jni::JDouble::valueOf(value.compressImageQuality.value()) : nullptr,
|
|
104
108
|
value.compressImageMaxWidth.has_value() ? jni::JDouble::valueOf(value.compressImageMaxWidth.value()) : nullptr,
|
|
105
109
|
value.compressImageMaxHeight.has_value() ? jni::JDouble::valueOf(value.compressImageMaxHeight.value()) : nullptr,
|
|
@@ -32,6 +32,9 @@ data class ImageCropPickerOptions(
|
|
|
32
32
|
val includeBase64: Boolean?,
|
|
33
33
|
@DoNotStrip
|
|
34
34
|
@Keep
|
|
35
|
+
val preserveOriginal: Boolean?,
|
|
36
|
+
@DoNotStrip
|
|
37
|
+
@Keep
|
|
35
38
|
val compressImageQuality: Double?,
|
|
36
39
|
@DoNotStrip
|
|
37
40
|
@Keep
|
|
@@ -73,6 +76,7 @@ data class ImageCropPickerOptions(
|
|
|
73
76
|
&& Objects.deepEquals(this.height, other.height)
|
|
74
77
|
&& Objects.deepEquals(this.cropping, other.cropping)
|
|
75
78
|
&& Objects.deepEquals(this.includeBase64, other.includeBase64)
|
|
79
|
+
&& Objects.deepEquals(this.preserveOriginal, other.preserveOriginal)
|
|
76
80
|
&& Objects.deepEquals(this.compressImageQuality, other.compressImageQuality)
|
|
77
81
|
&& Objects.deepEquals(this.compressImageMaxWidth, other.compressImageMaxWidth)
|
|
78
82
|
&& Objects.deepEquals(this.compressImageMaxHeight, other.compressImageMaxHeight)
|
|
@@ -92,6 +96,7 @@ data class ImageCropPickerOptions(
|
|
|
92
96
|
height,
|
|
93
97
|
cropping,
|
|
94
98
|
includeBase64,
|
|
99
|
+
preserveOriginal,
|
|
95
100
|
compressImageQuality,
|
|
96
101
|
compressImageMaxWidth,
|
|
97
102
|
compressImageMaxHeight,
|
|
@@ -114,8 +119,8 @@ data class ImageCropPickerOptions(
|
|
|
114
119
|
@Keep
|
|
115
120
|
@Suppress("unused")
|
|
116
121
|
@JvmStatic
|
|
117
|
-
private fun fromCpp(width: Double?, height: Double?, cropping: Boolean?, includeBase64: Boolean?, compressImageQuality: Double?, compressImageMaxWidth: Double?, compressImageMaxHeight: Double?, freeStyleCropEnabled: Boolean?, cropperCircleOverlay: Boolean?, cropperToolbarTitle: String?, cropperChooseText: String?, cropperCancelText: String?, cropperRotateButtonsHidden: Boolean?, showCropGuidelines: Boolean?, cropperAppearance: ImageCropperAppearance?): ImageCropPickerOptions {
|
|
118
|
-
return ImageCropPickerOptions(width, height, cropping, includeBase64, compressImageQuality, compressImageMaxWidth, compressImageMaxHeight, freeStyleCropEnabled, cropperCircleOverlay, cropperToolbarTitle, cropperChooseText, cropperCancelText, cropperRotateButtonsHidden, showCropGuidelines, cropperAppearance)
|
|
122
|
+
private fun fromCpp(width: Double?, height: Double?, cropping: Boolean?, includeBase64: Boolean?, preserveOriginal: Boolean?, compressImageQuality: Double?, compressImageMaxWidth: Double?, compressImageMaxHeight: Double?, freeStyleCropEnabled: Boolean?, cropperCircleOverlay: Boolean?, cropperToolbarTitle: String?, cropperChooseText: String?, cropperCancelText: String?, cropperRotateButtonsHidden: Boolean?, showCropGuidelines: Boolean?, cropperAppearance: ImageCropperAppearance?): ImageCropPickerOptions {
|
|
123
|
+
return ImageCropPickerOptions(width, height, cropping, includeBase64, preserveOriginal, compressImageQuality, compressImageMaxWidth, compressImageMaxHeight, freeStyleCropEnabled, cropperCircleOverlay, cropperToolbarTitle, cropperChooseText, cropperCancelText, cropperRotateButtonsHidden, showCropGuidelines, cropperAppearance)
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
}
|
|
@@ -18,7 +18,7 @@ public extension ImageCropPickerOptions {
|
|
|
18
18
|
/**
|
|
19
19
|
* Create a new instance of `ImageCropPickerOptions`.
|
|
20
20
|
*/
|
|
21
|
-
init(width: Double?, height: Double?, cropping: Bool?, includeBase64: Bool?, compressImageQuality: Double?, compressImageMaxWidth: Double?, compressImageMaxHeight: Double?, freeStyleCropEnabled: Bool?, cropperCircleOverlay: Bool?, cropperToolbarTitle: String?, cropperChooseText: String?, cropperCancelText: String?, cropperRotateButtonsHidden: Bool?, showCropGuidelines: Bool?, cropperAppearance: ImageCropperAppearance?) {
|
|
21
|
+
init(width: Double?, height: Double?, cropping: Bool?, includeBase64: Bool?, preserveOriginal: Bool?, compressImageQuality: Double?, compressImageMaxWidth: Double?, compressImageMaxHeight: Double?, freeStyleCropEnabled: Bool?, cropperCircleOverlay: Bool?, cropperToolbarTitle: String?, cropperChooseText: String?, cropperCancelText: String?, cropperRotateButtonsHidden: Bool?, showCropGuidelines: Bool?, cropperAppearance: ImageCropperAppearance?) {
|
|
22
22
|
self.init({ () -> bridge.std__optional_double_ in
|
|
23
23
|
if let __unwrappedValue = width {
|
|
24
24
|
return bridge.create_std__optional_double_(__unwrappedValue)
|
|
@@ -43,6 +43,12 @@ public extension ImageCropPickerOptions {
|
|
|
43
43
|
} else {
|
|
44
44
|
return .init()
|
|
45
45
|
}
|
|
46
|
+
}(), { () -> bridge.std__optional_bool_ in
|
|
47
|
+
if let __unwrappedValue = preserveOriginal {
|
|
48
|
+
return bridge.create_std__optional_bool_(__unwrappedValue)
|
|
49
|
+
} else {
|
|
50
|
+
return .init()
|
|
51
|
+
}
|
|
46
52
|
}(), { () -> bridge.std__optional_double_ in
|
|
47
53
|
if let __unwrappedValue = compressImageQuality {
|
|
48
54
|
return bridge.create_std__optional_double_(__unwrappedValue)
|
|
@@ -160,6 +166,18 @@ public extension ImageCropPickerOptions {
|
|
|
160
166
|
}()
|
|
161
167
|
}
|
|
162
168
|
|
|
169
|
+
@inline(__always)
|
|
170
|
+
var preserveOriginal: Bool? {
|
|
171
|
+
return { () -> Bool? in
|
|
172
|
+
if bridge.has_value_std__optional_bool_(self.__preserveOriginal) {
|
|
173
|
+
let __unwrapped = bridge.get_std__optional_bool_(self.__preserveOriginal)
|
|
174
|
+
return __unwrapped
|
|
175
|
+
} else {
|
|
176
|
+
return nil
|
|
177
|
+
}
|
|
178
|
+
}()
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
@inline(__always)
|
|
164
182
|
var compressImageQuality: Double? {
|
|
165
183
|
return { () -> Double? in
|
|
@@ -46,6 +46,7 @@ namespace margelo::nitro::reactnativeimagecroppicker {
|
|
|
46
46
|
std::optional<double> height SWIFT_PRIVATE;
|
|
47
47
|
std::optional<bool> cropping SWIFT_PRIVATE;
|
|
48
48
|
std::optional<bool> includeBase64 SWIFT_PRIVATE;
|
|
49
|
+
std::optional<bool> preserveOriginal SWIFT_PRIVATE;
|
|
49
50
|
std::optional<double> compressImageQuality SWIFT_PRIVATE;
|
|
50
51
|
std::optional<double> compressImageMaxWidth SWIFT_PRIVATE;
|
|
51
52
|
std::optional<double> compressImageMaxHeight SWIFT_PRIVATE;
|
|
@@ -60,7 +61,7 @@ namespace margelo::nitro::reactnativeimagecroppicker {
|
|
|
60
61
|
|
|
61
62
|
public:
|
|
62
63
|
ImageCropPickerOptions() = default;
|
|
63
|
-
explicit ImageCropPickerOptions(std::optional<double> width, std::optional<double> height, std::optional<bool> cropping, std::optional<bool> includeBase64, std::optional<double> compressImageQuality, std::optional<double> compressImageMaxWidth, std::optional<double> compressImageMaxHeight, std::optional<bool> freeStyleCropEnabled, std::optional<bool> cropperCircleOverlay, std::optional<std::string> cropperToolbarTitle, std::optional<std::string> cropperChooseText, std::optional<std::string> cropperCancelText, std::optional<bool> cropperRotateButtonsHidden, std::optional<bool> showCropGuidelines, std::optional<ImageCropperAppearance> cropperAppearance): width(width), height(height), cropping(cropping), includeBase64(includeBase64), compressImageQuality(compressImageQuality), compressImageMaxWidth(compressImageMaxWidth), compressImageMaxHeight(compressImageMaxHeight), freeStyleCropEnabled(freeStyleCropEnabled), cropperCircleOverlay(cropperCircleOverlay), cropperToolbarTitle(cropperToolbarTitle), cropperChooseText(cropperChooseText), cropperCancelText(cropperCancelText), cropperRotateButtonsHidden(cropperRotateButtonsHidden), showCropGuidelines(showCropGuidelines), cropperAppearance(cropperAppearance) {}
|
|
64
|
+
explicit ImageCropPickerOptions(std::optional<double> width, std::optional<double> height, std::optional<bool> cropping, std::optional<bool> includeBase64, std::optional<bool> preserveOriginal, std::optional<double> compressImageQuality, std::optional<double> compressImageMaxWidth, std::optional<double> compressImageMaxHeight, std::optional<bool> freeStyleCropEnabled, std::optional<bool> cropperCircleOverlay, std::optional<std::string> cropperToolbarTitle, std::optional<std::string> cropperChooseText, std::optional<std::string> cropperCancelText, std::optional<bool> cropperRotateButtonsHidden, std::optional<bool> showCropGuidelines, std::optional<ImageCropperAppearance> cropperAppearance): width(width), height(height), cropping(cropping), includeBase64(includeBase64), preserveOriginal(preserveOriginal), compressImageQuality(compressImageQuality), compressImageMaxWidth(compressImageMaxWidth), compressImageMaxHeight(compressImageMaxHeight), freeStyleCropEnabled(freeStyleCropEnabled), cropperCircleOverlay(cropperCircleOverlay), cropperToolbarTitle(cropperToolbarTitle), cropperChooseText(cropperChooseText), cropperCancelText(cropperCancelText), cropperRotateButtonsHidden(cropperRotateButtonsHidden), showCropGuidelines(showCropGuidelines), cropperAppearance(cropperAppearance) {}
|
|
64
65
|
|
|
65
66
|
public:
|
|
66
67
|
friend bool operator==(const ImageCropPickerOptions& lhs, const ImageCropPickerOptions& rhs) = default;
|
|
@@ -80,6 +81,7 @@ namespace margelo::nitro {
|
|
|
80
81
|
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "height"))),
|
|
81
82
|
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cropping"))),
|
|
82
83
|
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "includeBase64"))),
|
|
84
|
+
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "preserveOriginal"))),
|
|
83
85
|
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compressImageQuality"))),
|
|
84
86
|
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compressImageMaxWidth"))),
|
|
85
87
|
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compressImageMaxHeight"))),
|
|
@@ -99,6 +101,7 @@ namespace margelo::nitro {
|
|
|
99
101
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "height"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.height));
|
|
100
102
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "cropping"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.cropping));
|
|
101
103
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "includeBase64"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.includeBase64));
|
|
104
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "preserveOriginal"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.preserveOriginal));
|
|
102
105
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "compressImageQuality"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.compressImageQuality));
|
|
103
106
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "compressImageMaxWidth"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.compressImageMaxWidth));
|
|
104
107
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "compressImageMaxHeight"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.compressImageMaxHeight));
|
|
@@ -124,6 +127,7 @@ namespace margelo::nitro {
|
|
|
124
127
|
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "height")))) return false;
|
|
125
128
|
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cropping")))) return false;
|
|
126
129
|
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "includeBase64")))) return false;
|
|
130
|
+
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "preserveOriginal")))) return false;
|
|
127
131
|
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compressImageQuality")))) return false;
|
|
128
132
|
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compressImageMaxWidth")))) return false;
|
|
129
133
|
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "compressImageMaxHeight")))) return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-image-crop-picker",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.165",
|
|
4
4
|
"description": "Single photo picker and cropper Nitro module for OneKey, replacing react-native-image-crop-picker",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -53,14 +53,15 @@
|
|
|
53
53
|
],
|
|
54
54
|
"repository": {
|
|
55
55
|
"type": "git",
|
|
56
|
-
"url": "git+https://github.com/OneKeyHQ/app-modules
|
|
56
|
+
"url": "git+https://github.com/OneKeyHQ/app-modules.git",
|
|
57
|
+
"directory": "native-modules/react-native-image-crop-picker"
|
|
57
58
|
},
|
|
58
59
|
"author": "onekeyfe <huanming@onekey.so> (https://github.com/OneKeyHQ/app-modules)",
|
|
59
60
|
"license": "MIT",
|
|
60
61
|
"bugs": {
|
|
61
|
-
"url": "https://github.com/OneKeyHQ/app-modules/
|
|
62
|
+
"url": "https://github.com/OneKeyHQ/app-modules/issues"
|
|
62
63
|
},
|
|
63
|
-
"homepage": "https://github.com/OneKeyHQ/app-modules/react-native-image-crop-picker#readme",
|
|
64
|
+
"homepage": "https://github.com/OneKeyHQ/app-modules/tree/main/native-modules/react-native-image-crop-picker#readme",
|
|
64
65
|
"publishConfig": {
|
|
65
66
|
"registry": "https://registry.npmjs.org/"
|
|
66
67
|
},
|
|
@@ -133,7 +134,19 @@
|
|
|
133
134
|
"modulePathIgnorePatterns": [
|
|
134
135
|
"<rootDir>/example/node_modules",
|
|
135
136
|
"<rootDir>/lib/"
|
|
136
|
-
]
|
|
137
|
+
],
|
|
138
|
+
"transform": {
|
|
139
|
+
"^.+\\.[jt]sx?$": [
|
|
140
|
+
"babel-jest",
|
|
141
|
+
{
|
|
142
|
+
"presets": [
|
|
143
|
+
"module:@react-native/babel-preset"
|
|
144
|
+
],
|
|
145
|
+
"babelrc": false,
|
|
146
|
+
"configFile": false
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
137
150
|
},
|
|
138
151
|
"commitlint": {
|
|
139
152
|
"extends": [
|
|
@@ -52,6 +52,8 @@ export interface ImageCropPickerOptions {
|
|
|
52
52
|
height?: number;
|
|
53
53
|
cropping?: boolean;
|
|
54
54
|
includeBase64?: boolean;
|
|
55
|
+
// Copy the selected representation without re-encoding when not cropping.
|
|
56
|
+
preserveOriginal?: boolean;
|
|
55
57
|
compressImageQuality?: number;
|
|
56
58
|
compressImageMaxWidth?: number;
|
|
57
59
|
compressImageMaxHeight?: number;
|
package/src/index.tsx
CHANGED
|
@@ -74,7 +74,7 @@ function toImageCropPickerError(error: unknown): ImageCropPickerError {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Options accepted for source compatibility with react-native-image-crop-picker.
|
|
77
|
-
// Only single photos are supported
|
|
77
|
+
// Only single photos are supported; processed results are JPEG.
|
|
78
78
|
export interface Options extends ImageCropPickerOptions {
|
|
79
79
|
mediaType?: 'photo';
|
|
80
80
|
multiple?: false;
|