react-native-customizable-image-crop-picker 1.0.1
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/LICENSE +44 -0
- package/README.md +479 -0
- package/RNCustomizableImageCropPicker.podspec +26 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/.transforms/a34a6297c7e9b65c987429ecd018e9a9/results.bin +1 -0
- package/android/build/.transforms/a34a6297c7e9b65c987429ecd018e9a9/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/results.bin +1 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/BuildConfig.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/NativeImageCropperActivity$Companion.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/NativeImageCropperActivity.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/NativeImageCropperModule$Companion.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/NativeImageCropperModule.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/NativeImageCropperPackage.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/rncustomizableimagecroppicker/NativeImagePickerFileProvider.dex +0 -0
- package/android/build/.transforms/d07257a7807cb64bf14f5adb0f98ee25/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
- package/android/build/generated/source/buildConfig/debug/com/rncustomizableimagecroppicker/BuildConfig.java +10 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +26 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +1 -0
- package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/xml_nativeimagepicker_file_paths.xml.flat +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/react-native-customizable-image-crop-picker_debug.kotlin_module +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/rncustomizableimagecroppicker/BuildConfig.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +3 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +41 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +26 -0
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/packaged_res/debug/packageDebugResources/xml/nativeimagepicker_file_paths.xml +6 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/META-INF/react-native-customizable-image-crop-picker_debug.kotlin_module +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/NativeImageCropperActivity$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/NativeImageCropperActivity.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/NativeImageCropperModule$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/NativeImageCropperModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/NativeImageCropperPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/rncustomizableimagecroppicker/NativeImagePickerFileProvider.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +2 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.s +1 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +1 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +2 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.s +1 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +46 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/META-INF/react-native-customizable-image-crop-picker_debug.kotlin_module +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/rncustomizableimagecroppicker/NativeImageCropperActivity$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/rncustomizableimagecroppicker/NativeImageCropperActivity.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/rncustomizableimagecroppicker/NativeImageCropperModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/rncustomizableimagecroppicker/NativeImageCropperModule.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/rncustomizableimagecroppicker/NativeImageCropperPackage.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/rncustomizableimagecroppicker/NativeImagePickerFileProvider.class +0 -0
- package/android/build.gradle +49 -0
- package/android/consumer-rules.pro +3 -0
- package/android/src/main/AndroidManifest.xml +23 -0
- package/android/src/main/java/com/rncustomizableimagecroppicker/NativeImageCropperActivity.kt +1009 -0
- package/android/src/main/java/com/rncustomizableimagecroppicker/NativeImageCropperModule.kt +684 -0
- package/android/src/main/java/com/rncustomizableimagecroppicker/NativeImageCropperPackage.kt +17 -0
- package/android/src/main/java/com/rncustomizableimagecroppicker/NativeImagePickerFileProvider.kt +6 -0
- package/android/src/main/res/xml/nativeimagepicker_file_paths.xml +6 -0
- package/ios/NativeImageCropperModule.m +13 -0
- package/ios/NativeImageCropperModule.swift +1400 -0
- package/package.json +116 -0
- package/react-native.config.js +13 -0
- package/src/api.ts +41 -0
- package/src/errors.ts +39 -0
- package/src/index.ts +4 -0
- package/src/native/NativeImageCropperModule.ts +34 -0
- package/src/native/mapOptions.ts +164 -0
- package/src/types.ts +258 -0
- package/src/ui/ImageCropPickerModal.tsx +322 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
package com.rncustomizableimagecroppicker
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.Manifest
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.graphics.Color
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import android.provider.MediaStore
|
|
10
|
+
import android.util.Base64
|
|
11
|
+
import android.util.Base64OutputStream
|
|
12
|
+
import androidx.activity.result.PickVisualMediaRequest
|
|
13
|
+
import androidx.activity.result.contract.ActivityResultContracts
|
|
14
|
+
import androidx.core.content.ContextCompat
|
|
15
|
+
import androidx.core.content.FileProvider
|
|
16
|
+
import com.facebook.react.bridge.Promise
|
|
17
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
18
|
+
import com.facebook.react.bridge.ActivityEventListener
|
|
19
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
20
|
+
import com.facebook.react.bridge.ReactMethod
|
|
21
|
+
import com.facebook.react.bridge.ReadableMap
|
|
22
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
23
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
24
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
25
|
+
import com.yalantis.ucrop.UCrop
|
|
26
|
+
import java.io.ByteArrayOutputStream
|
|
27
|
+
import java.io.File
|
|
28
|
+
import java.util.UUID
|
|
29
|
+
import java.util.concurrent.ExecutorService
|
|
30
|
+
import java.util.concurrent.Executors
|
|
31
|
+
|
|
32
|
+
@ReactModule(name = NativeImageCropperModule.NAME)
|
|
33
|
+
class NativeImageCropperModule(private val reactContext: ReactApplicationContext) :
|
|
34
|
+
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
35
|
+
|
|
36
|
+
companion object {
|
|
37
|
+
const val NAME = "NativeImageCropperModule"
|
|
38
|
+
private const val REQUEST_PICK_IMAGE = 63201
|
|
39
|
+
private const val REQUEST_CAPTURE_IMAGE = 63202
|
|
40
|
+
|
|
41
|
+
private const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
|
|
42
|
+
private const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
|
|
43
|
+
private const val E_PICKER_CANCELLED_MSG = "User cancelled image selection"
|
|
44
|
+
private const val E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND"
|
|
45
|
+
private const val E_MODULE_DESTROYED = "E_MODULE_DESTROYED"
|
|
46
|
+
private const val E_PICKER_ERROR = "E_PICKER_ERROR"
|
|
47
|
+
private const val E_PERMISSION_MISSING = "E_PERMISSION_MISSING"
|
|
48
|
+
private const val E_NO_APP_AVAILABLE = "E_NO_APP_AVAILABLE"
|
|
49
|
+
|
|
50
|
+
private const val DEFAULT_CROP_SIZE = 100
|
|
51
|
+
private const val SOURCE_CAMERA = "camera"
|
|
52
|
+
private const val SOURCE_GALLERY = "gallery"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun getName(): String = NAME
|
|
56
|
+
|
|
57
|
+
// Required by React Native when using NativeEventEmitter on Android.
|
|
58
|
+
// (No-op; we emit events opportunistically during base64 encoding.)
|
|
59
|
+
@ReactMethod
|
|
60
|
+
fun addListener(eventName: String) = Unit
|
|
61
|
+
|
|
62
|
+
@ReactMethod
|
|
63
|
+
fun removeListeners(count: Int) = Unit
|
|
64
|
+
|
|
65
|
+
private var pendingPromise: Promise? = null
|
|
66
|
+
private val encodeExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
|
67
|
+
@Volatile private var isInvalidated = false
|
|
68
|
+
|
|
69
|
+
private var cropWidth = DEFAULT_CROP_SIZE
|
|
70
|
+
private var cropHeight = DEFAULT_CROP_SIZE
|
|
71
|
+
private var headerTitle = "Preview Image"
|
|
72
|
+
private var cancelText = "Cancel"
|
|
73
|
+
private var uploadText = "Upload"
|
|
74
|
+
// Defaults aligned with the "demo button layout":
|
|
75
|
+
// - Cancel is a light button with dark text
|
|
76
|
+
// - Upload is a black button with white text
|
|
77
|
+
private var cancelColor = "#111111" // text color
|
|
78
|
+
private var uploadColor = "#111111" // background color
|
|
79
|
+
private var dimmedLayerColor = "#B3000000"
|
|
80
|
+
private var statusBarColor = "#FFFFFF"
|
|
81
|
+
private var statusBarStyle = "auto" // "dark" | "light" | "auto"
|
|
82
|
+
private var isDarkTheme = false
|
|
83
|
+
private var drawUnderStatusBar = false
|
|
84
|
+
private var pickerSource = SOURCE_GALLERY
|
|
85
|
+
private var pendingCameraUri: Uri? = null
|
|
86
|
+
private var includeBase64 = false
|
|
87
|
+
private var freeStyleCropEnabled = false
|
|
88
|
+
private var compressQuality = 100 // 0..100
|
|
89
|
+
private var compressFormat = "jpeg" // jpeg | png | webp
|
|
90
|
+
private var circularCrop = false
|
|
91
|
+
private var rotationEnabled = false
|
|
92
|
+
private var cropGridEnabled = false
|
|
93
|
+
private var cropFrameColor = ""
|
|
94
|
+
private var cropGridColor = ""
|
|
95
|
+
private var showNativeCropControls = false
|
|
96
|
+
|
|
97
|
+
private var headerBackgroundColor = "#FFFFFF"
|
|
98
|
+
private var headerTitleColor = "#111111"
|
|
99
|
+
private var headerTitleFontSize = 22
|
|
100
|
+
private var headerTitleFontFamily = ""
|
|
101
|
+
private var headerHeight = 84
|
|
102
|
+
private var headerPaddingHorizontal = 20
|
|
103
|
+
private var headerPaddingTop = 20
|
|
104
|
+
private var headerPaddingBottom = 20
|
|
105
|
+
|
|
106
|
+
private var buttonContainerBackgroundColor = "#FFFFFF"
|
|
107
|
+
private var buttonContainerPaddingHorizontal = 20
|
|
108
|
+
private var buttonContainerPaddingTop = 16
|
|
109
|
+
private var buttonContainerPaddingBottom = 24
|
|
110
|
+
private var buttonGap = 12
|
|
111
|
+
private var buttonHeight = 54
|
|
112
|
+
private var buttonLayout = "vertical"
|
|
113
|
+
|
|
114
|
+
private var cancelButtonBackgroundColor = "#EFEFF4"
|
|
115
|
+
private var cancelButtonBorderColor = "#EFEFF4"
|
|
116
|
+
private var cancelButtonBorderWidth = 0
|
|
117
|
+
private var cancelButtonFontSize = 18
|
|
118
|
+
private var cancelButtonFontFamily = ""
|
|
119
|
+
private var cancelButtonRadius = 28
|
|
120
|
+
|
|
121
|
+
private var uploadButtonTextColor = "#FFFFFF"
|
|
122
|
+
private var uploadButtonBackgroundColor = "#111111"
|
|
123
|
+
private var uploadButtonBorderColor = "#111111"
|
|
124
|
+
private var uploadButtonBorderWidth = 0
|
|
125
|
+
private var uploadButtonFontSize = 18
|
|
126
|
+
private var uploadButtonFontFamily = ""
|
|
127
|
+
private var uploadButtonRadius = 28
|
|
128
|
+
|
|
129
|
+
private var controlsPlacement = "bottom"
|
|
130
|
+
private var topLeftControl = "cancel"
|
|
131
|
+
private var topRightControl = "upload"
|
|
132
|
+
private var footerButtonOrder = "uploadFirst"
|
|
133
|
+
|
|
134
|
+
private var cancelButtonContent = "text"
|
|
135
|
+
private var cancelButtonIconUri = ""
|
|
136
|
+
private var cancelButtonIconBase64 = ""
|
|
137
|
+
private var cancelButtonIconTintColor = ""
|
|
138
|
+
private var cancelButtonIconSize = 18
|
|
139
|
+
private var cancelButtonIconGap = 8
|
|
140
|
+
private var cancelButtonPaddingHorizontal = 12
|
|
141
|
+
private var cancelButtonPaddingVertical = 0
|
|
142
|
+
|
|
143
|
+
private var uploadButtonContent = "text"
|
|
144
|
+
private var uploadButtonIconUri = ""
|
|
145
|
+
private var uploadButtonIconBase64 = ""
|
|
146
|
+
private var uploadButtonIconTintColor = ""
|
|
147
|
+
private var uploadButtonIconSize = 18
|
|
148
|
+
private var uploadButtonIconGap = 8
|
|
149
|
+
private var uploadButtonPaddingHorizontal = 12
|
|
150
|
+
private var uploadButtonPaddingVertical = 0
|
|
151
|
+
|
|
152
|
+
private var headerAlignment = "left" // "left" | "center" | "right"
|
|
153
|
+
|
|
154
|
+
init {
|
|
155
|
+
reactContext.addActivityEventListener(this)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@ReactMethod
|
|
159
|
+
fun openImagePreview(options: ReadableMap, promise: Promise) {
|
|
160
|
+
if (isInvalidated) {
|
|
161
|
+
promise.reject(E_MODULE_DESTROYED, "Native image cropper module is destroyed")
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
val activity = reactApplicationContext.currentActivity
|
|
165
|
+
if (activity == null) {
|
|
166
|
+
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist")
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
if (pendingPromise != null) {
|
|
170
|
+
promise.reject(E_PICKER_ERROR, "Image picker is already in progress")
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
applyConfig(options)
|
|
175
|
+
pendingPromise = promise
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
if (pickerSource == SOURCE_CAMERA) {
|
|
179
|
+
launchCamera(activity)
|
|
180
|
+
} else {
|
|
181
|
+
launchSystemPhotoPicker(activity)
|
|
182
|
+
}
|
|
183
|
+
} catch (error: Exception) {
|
|
184
|
+
rejectPending(E_PICKER_ERROR, error.message ?: "Unable to open image picker")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
189
|
+
when (requestCode) {
|
|
190
|
+
REQUEST_PICK_IMAGE -> handleImagePickResult(activity, resultCode, data)
|
|
191
|
+
REQUEST_CAPTURE_IMAGE -> handleCameraResult(activity, resultCode, data)
|
|
192
|
+
UCrop.REQUEST_CROP -> handleCropResult(resultCode, data)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
override fun onNewIntent(intent: Intent) = Unit
|
|
197
|
+
|
|
198
|
+
override fun invalidate() {
|
|
199
|
+
isInvalidated = true
|
|
200
|
+
reactContext.removeActivityEventListener(this)
|
|
201
|
+
pendingCameraUri = null
|
|
202
|
+
rejectPending(E_MODULE_DESTROYED, "Native image cropper module is destroyed")
|
|
203
|
+
encodeExecutor.shutdownNow()
|
|
204
|
+
super.invalidate()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun applyConfig(options: ReadableMap) {
|
|
208
|
+
isDarkTheme = options.hasKey("isDarkTheme") && options.getBoolean("isDarkTheme")
|
|
209
|
+
includeBase64 = options.hasKey("includeBase64") && options.getBoolean("includeBase64")
|
|
210
|
+
compressQuality = runCatching {
|
|
211
|
+
if (options.hasKey("compressQuality") && !options.isNull("compressQuality")) {
|
|
212
|
+
val q = options.getDouble("compressQuality").coerceIn(0.0, 1.0)
|
|
213
|
+
kotlin.math.round(q * 100.0).toInt().coerceIn(0, 100)
|
|
214
|
+
} else {
|
|
215
|
+
100
|
|
216
|
+
}
|
|
217
|
+
}.getOrDefault(100)
|
|
218
|
+
compressFormat = options.getStringOrDefault("compressFormat", "jpeg").let { raw ->
|
|
219
|
+
when (raw.lowercase()) {
|
|
220
|
+
"png" -> "png"
|
|
221
|
+
"webp" -> "webp"
|
|
222
|
+
else -> "jpeg"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
circularCrop = options.hasKey("circularCrop") && options.getBoolean("circularCrop")
|
|
226
|
+
rotationEnabled = options.hasKey("rotationEnabled") && options.getBoolean("rotationEnabled")
|
|
227
|
+
cropGridEnabled = options.hasKey("cropGridEnabled") && options.getBoolean("cropGridEnabled")
|
|
228
|
+
cropFrameColor = options.getStringOrDefault("cropFrameColor", "")
|
|
229
|
+
cropGridColor = options.getStringOrDefault("cropGridColor", "")
|
|
230
|
+
showNativeCropControls = options.hasKey("showNativeCropControls") && options.getBoolean("showNativeCropControls")
|
|
231
|
+
if (rotationEnabled) showNativeCropControls = true
|
|
232
|
+
drawUnderStatusBar = options.hasKey("drawUnderStatusBar") && options.getBoolean("drawUnderStatusBar")
|
|
233
|
+
freeStyleCropEnabled = options.hasKey("freeStyleCropEnabled") && options.getBoolean("freeStyleCropEnabled")
|
|
234
|
+
cropWidth = sanitizePositive(options.getIntOrDefault("width", DEFAULT_CROP_SIZE), DEFAULT_CROP_SIZE)
|
|
235
|
+
cropHeight = sanitizePositive(options.getIntOrDefault("height", DEFAULT_CROP_SIZE), DEFAULT_CROP_SIZE)
|
|
236
|
+
headerTitle = options.getStringOrDefault("cropperToolbarTitle", "Preview Image")
|
|
237
|
+
cancelText = options.getStringOrDefault("cropperCancelText", "Cancel")
|
|
238
|
+
uploadText = options.getStringOrDefault("cropperChooseText", "Upload")
|
|
239
|
+
cancelColor = options.getStringOrDefault("cropperCancelColor", "#111111")
|
|
240
|
+
uploadColor = options.getStringOrDefault("cropperChooseColor", "#111111")
|
|
241
|
+
dimmedLayerColor = options.getStringOrDefault(
|
|
242
|
+
"cropperDimmedLayerColor",
|
|
243
|
+
if (isDarkTheme) "#E0000000" else "#B3000000",
|
|
244
|
+
)
|
|
245
|
+
pickerSource = options.getStringOrDefault("pickerSource", SOURCE_GALLERY)
|
|
246
|
+
|
|
247
|
+
controlsPlacement = options.getStringOrDefault("controlsPlacement", "bottom").let { raw ->
|
|
248
|
+
if (raw == "top") "top" else "bottom"
|
|
249
|
+
}
|
|
250
|
+
if (showNativeCropControls) controlsPlacement = "top"
|
|
251
|
+
topLeftControl = options.getStringOrDefault("topLeftControl", "cancel").let { raw ->
|
|
252
|
+
if (raw == "upload" || raw == "cancel" || raw == "none") raw else "cancel"
|
|
253
|
+
}
|
|
254
|
+
topRightControl = options.getStringOrDefault("topRightControl", "upload").let { raw ->
|
|
255
|
+
if (raw == "upload" || raw == "cancel" || raw == "none") raw else "upload"
|
|
256
|
+
}
|
|
257
|
+
footerButtonOrder = options.getStringOrDefault("footerButtonOrder", "uploadFirst").let { raw ->
|
|
258
|
+
if (raw == "cancelFirst") "cancelFirst" else "uploadFirst"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
headerAlignment = options.getStringOrDefault("headerAlignment", "left").let { raw ->
|
|
262
|
+
if (raw == "center" || raw == "right" || raw == "left") raw else "left"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
val headerStyle = getOptionalMap(options, "headerStyle")
|
|
266
|
+
headerBackgroundColor = getMapString(headerStyle, "backgroundColor", if (isDarkTheme) "#0B0B0F" else "#FFFFFF")
|
|
267
|
+
headerTitleColor = getMapString(
|
|
268
|
+
headerStyle,
|
|
269
|
+
"color",
|
|
270
|
+
getMapString(headerStyle, "titleColor", if (isDarkTheme) "#F5F5F7" else "#111111"),
|
|
271
|
+
)
|
|
272
|
+
headerTitleFontSize = sanitizePositive(getMapInt(headerStyle, "fontSize", 22), 22)
|
|
273
|
+
headerTitleFontFamily = getMapString(headerStyle, "fontFamily", "")
|
|
274
|
+
val defaultHeaderHeight = if (controlsPlacement == "top") 84 else 56
|
|
275
|
+
headerHeight = sanitizePositive(getMapInt(headerStyle, "height", defaultHeaderHeight), defaultHeaderHeight)
|
|
276
|
+
headerPaddingHorizontal = sanitizeNonNegative(getMapInt(headerStyle, "paddingHorizontal", 20), 20)
|
|
277
|
+
val defaultHeaderPadTop = if (controlsPlacement == "top") 20 else 12
|
|
278
|
+
val defaultHeaderPadBottom = if (controlsPlacement == "top") 20 else 12
|
|
279
|
+
headerPaddingTop = sanitizeNonNegative(getMapInt(headerStyle, "paddingTop", defaultHeaderPadTop), defaultHeaderPadTop)
|
|
280
|
+
headerPaddingBottom = sanitizeNonNegative(getMapInt(headerStyle, "paddingBottom", defaultHeaderPadBottom), defaultHeaderPadBottom)
|
|
281
|
+
|
|
282
|
+
// Default status bar color to header background to avoid a visible seam.
|
|
283
|
+
// Explicit `statusBarColor` (cropperStatusBarColor) still overrides this.
|
|
284
|
+
statusBarColor = options.getStringOrDefault("cropperStatusBarColor", headerBackgroundColor)
|
|
285
|
+
statusBarStyle = options.getStringOrDefault("cropperStatusBarStyle", "auto").let { raw ->
|
|
286
|
+
if (raw == "dark" || raw == "light") raw else "auto"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
val containerStyle = getOptionalMap(options, "buttonContainerStyle")
|
|
290
|
+
buttonContainerBackgroundColor = getMapString(containerStyle, "backgroundColor", "#FFFFFF")
|
|
291
|
+
buttonContainerPaddingHorizontal = sanitizeNonNegative(getMapInt(containerStyle, "paddingHorizontal", 20), 20)
|
|
292
|
+
buttonContainerPaddingTop = sanitizeNonNegative(getMapInt(containerStyle, "paddingTop", 16), 16)
|
|
293
|
+
buttonContainerPaddingBottom = sanitizeNonNegative(getMapInt(containerStyle, "paddingBottom", 24), 24)
|
|
294
|
+
buttonGap = sanitizeNonNegative(getMapInt(containerStyle, "gap", 12), 12)
|
|
295
|
+
buttonHeight = sanitizePositive(getMapInt(containerStyle, "buttonHeight", 54), 54)
|
|
296
|
+
buttonLayout = getMapString(containerStyle, "layout", "vertical").let { raw ->
|
|
297
|
+
if (raw == "horizontal") "horizontal" else "vertical"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
val cancelStyle = getOptionalMap(options, "cancelButtonStyle")
|
|
301
|
+
cancelButtonBackgroundColor = getMapString(cancelStyle, "backgroundColor", cancelButtonBackgroundColor)
|
|
302
|
+
cancelButtonBorderColor = getMapString(cancelStyle, "borderColor", cancelButtonBorderColor)
|
|
303
|
+
cancelButtonBorderWidth = sanitizeNonNegative(getMapInt(cancelStyle, "borderWidth", cancelButtonBorderWidth), cancelButtonBorderWidth)
|
|
304
|
+
cancelButtonFontSize = sanitizePositive(getMapInt(cancelStyle, "fontSize", cancelButtonFontSize), cancelButtonFontSize)
|
|
305
|
+
cancelButtonFontFamily = getMapString(cancelStyle, "fontFamily", cancelButtonFontFamily)
|
|
306
|
+
cancelButtonRadius = sanitizeNonNegative(getMapInt(cancelStyle, "borderRadius", cancelButtonRadius), cancelButtonRadius)
|
|
307
|
+
cancelColor = getMapString(cancelStyle, "textColor", cancelColor)
|
|
308
|
+
cancelButtonContent = getMapString(cancelStyle, "content", cancelButtonContent)
|
|
309
|
+
cancelButtonIconUri = getMapString(cancelStyle, "iconUri", cancelButtonIconUri)
|
|
310
|
+
cancelButtonIconBase64 = getMapString(cancelStyle, "iconBase64", cancelButtonIconBase64)
|
|
311
|
+
cancelButtonIconTintColor = getMapString(cancelStyle, "iconTintColor", cancelButtonIconTintColor)
|
|
312
|
+
cancelButtonIconSize = sanitizePositive(getMapInt(cancelStyle, "iconSize", cancelButtonIconSize), cancelButtonIconSize)
|
|
313
|
+
cancelButtonIconGap = sanitizeNonNegative(getMapInt(cancelStyle, "iconGap", cancelButtonIconGap), cancelButtonIconGap)
|
|
314
|
+
cancelButtonPaddingHorizontal = sanitizeNonNegative(getMapInt(cancelStyle, "paddingHorizontal", cancelButtonPaddingHorizontal), cancelButtonPaddingHorizontal)
|
|
315
|
+
cancelButtonPaddingVertical = sanitizeNonNegative(getMapInt(cancelStyle, "paddingVertical", cancelButtonPaddingVertical), cancelButtonPaddingVertical)
|
|
316
|
+
|
|
317
|
+
val uploadStyle = getOptionalMap(options, "uploadButtonStyle")
|
|
318
|
+
uploadButtonBackgroundColor = getMapString(uploadStyle, "backgroundColor", uploadButtonBackgroundColor)
|
|
319
|
+
uploadButtonBorderColor = getMapString(uploadStyle, "borderColor", uploadButtonBorderColor)
|
|
320
|
+
uploadButtonBorderWidth = sanitizeNonNegative(getMapInt(uploadStyle, "borderWidth", uploadButtonBorderWidth), uploadButtonBorderWidth)
|
|
321
|
+
uploadButtonFontSize = sanitizePositive(getMapInt(uploadStyle, "fontSize", uploadButtonFontSize), uploadButtonFontSize)
|
|
322
|
+
uploadButtonFontFamily = getMapString(uploadStyle, "fontFamily", uploadButtonFontFamily)
|
|
323
|
+
uploadButtonRadius = sanitizeNonNegative(getMapInt(uploadStyle, "borderRadius", uploadButtonRadius), uploadButtonRadius)
|
|
324
|
+
uploadButtonTextColor = getMapString(uploadStyle, "textColor", uploadButtonTextColor)
|
|
325
|
+
uploadColor = uploadButtonBackgroundColor
|
|
326
|
+
uploadButtonContent = getMapString(uploadStyle, "content", uploadButtonContent)
|
|
327
|
+
uploadButtonIconUri = getMapString(uploadStyle, "iconUri", uploadButtonIconUri)
|
|
328
|
+
uploadButtonIconBase64 = getMapString(uploadStyle, "iconBase64", uploadButtonIconBase64)
|
|
329
|
+
uploadButtonIconTintColor = getMapString(uploadStyle, "iconTintColor", uploadButtonIconTintColor)
|
|
330
|
+
uploadButtonIconSize = sanitizePositive(getMapInt(uploadStyle, "iconSize", uploadButtonIconSize), uploadButtonIconSize)
|
|
331
|
+
uploadButtonIconGap = sanitizeNonNegative(getMapInt(uploadStyle, "iconGap", uploadButtonIconGap), uploadButtonIconGap)
|
|
332
|
+
uploadButtonPaddingHorizontal = sanitizeNonNegative(getMapInt(uploadStyle, "paddingHorizontal", uploadButtonPaddingHorizontal), uploadButtonPaddingHorizontal)
|
|
333
|
+
uploadButtonPaddingVertical = sanitizeNonNegative(getMapInt(uploadStyle, "paddingVertical", uploadButtonPaddingVertical), uploadButtonPaddingVertical)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private fun handleImagePickResult(activity: Activity, resultCode: Int, data: Intent?) {
|
|
337
|
+
if (pendingPromise == null) return
|
|
338
|
+
if (resultCode == Activity.RESULT_CANCELED) {
|
|
339
|
+
rejectPending(E_PICKER_CANCELLED, E_PICKER_CANCELLED_MSG)
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
val sourceUri = data?.data
|
|
343
|
+
if (resultCode != Activity.RESULT_OK || sourceUri == null) {
|
|
344
|
+
rejectPending(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url")
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
startCrop(activity, sourceUri)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private fun handleCameraResult(activity: Activity, resultCode: Int, data: Intent?) {
|
|
351
|
+
if (pendingPromise == null) return
|
|
352
|
+
if (resultCode == Activity.RESULT_CANCELED) {
|
|
353
|
+
cleanupCameraUriPermissions(activity)
|
|
354
|
+
pendingCameraUri = null
|
|
355
|
+
rejectPending(E_PICKER_CANCELLED, E_PICKER_CANCELLED_MSG)
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
val sourceUri = pendingCameraUri ?: data?.data
|
|
359
|
+
cleanupCameraUriPermissions(activity)
|
|
360
|
+
pendingCameraUri = null
|
|
361
|
+
if (resultCode != Activity.RESULT_OK || sourceUri == null) {
|
|
362
|
+
rejectPending(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url")
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
startCrop(activity, sourceUri)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private fun startCrop(activity: Activity, sourceUri: Uri) {
|
|
369
|
+
try {
|
|
370
|
+
val ext = when (compressFormat) {
|
|
371
|
+
"png" -> "png"
|
|
372
|
+
"webp" -> "webp"
|
|
373
|
+
else -> "jpg"
|
|
374
|
+
}
|
|
375
|
+
val destinationFile = File(activity.cacheDir, "native-cropped-${UUID.randomUUID()}.$ext")
|
|
376
|
+
val destinationUri = Uri.fromFile(destinationFile)
|
|
377
|
+
|
|
378
|
+
val cropOptions = UCrop.Options().apply {
|
|
379
|
+
val fmt = when (compressFormat) {
|
|
380
|
+
"png" -> Bitmap.CompressFormat.PNG
|
|
381
|
+
"webp" -> {
|
|
382
|
+
if (android.os.Build.VERSION.SDK_INT >= 30) {
|
|
383
|
+
Bitmap.CompressFormat.WEBP_LOSSY
|
|
384
|
+
} else {
|
|
385
|
+
@Suppress("DEPRECATION")
|
|
386
|
+
Bitmap.CompressFormat.WEBP
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else -> Bitmap.CompressFormat.JPEG
|
|
390
|
+
}
|
|
391
|
+
setCompressionFormat(fmt)
|
|
392
|
+
// PNG ignores quality but uCrop API requires an int.
|
|
393
|
+
setCompressionQuality(compressQuality.coerceIn(0, 100))
|
|
394
|
+
setToolbarTitle(headerTitle.ifBlank { "Preview Image" })
|
|
395
|
+
setToolbarColor(if (isDarkTheme) Color.parseColor("#121212") else Color.parseColor("#FFFFFF"))
|
|
396
|
+
setStatusBarColor(parseColor(statusBarColor, Color.WHITE))
|
|
397
|
+
setToolbarWidgetColor(if (isDarkTheme) Color.parseColor("#FFFFFF") else Color.parseColor("#111111"))
|
|
398
|
+
setShowCropGrid(cropGridEnabled)
|
|
399
|
+
setShowCropFrame(true)
|
|
400
|
+
if (cropFrameColor.isNotBlank()) setCropFrameColor(parseColor(cropFrameColor, Color.WHITE))
|
|
401
|
+
if (cropGridColor.isNotBlank()) setCropGridColor(parseColor(cropGridColor, Color.WHITE))
|
|
402
|
+
setDimmedLayerColor(parseColor(dimmedLayerColor, Color.parseColor("#B3000000")))
|
|
403
|
+
setCircleDimmedLayer(circularCrop)
|
|
404
|
+
setHideBottomControls(!showNativeCropControls)
|
|
405
|
+
setFreeStyleCropEnabled(freeStyleCropEnabled)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
val cropIntent = UCrop.of(sourceUri, destinationUri)
|
|
409
|
+
.withOptions(cropOptions)
|
|
410
|
+
.withAspectRatio(cropWidth.toFloat(), cropHeight.toFloat())
|
|
411
|
+
.getIntent(activity)
|
|
412
|
+
.apply {
|
|
413
|
+
setClass(activity, NativeImageCropperActivity::class.java)
|
|
414
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_TITLE, headerTitle.ifBlank { "Preview Image" })
|
|
415
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_TEXT, cancelText.ifBlank { "Cancel" })
|
|
416
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_TEXT, uploadText.ifBlank { "Upload" })
|
|
417
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_COLOR, cancelColor.ifBlank { "#111111" })
|
|
418
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_COLOR, uploadColor.ifBlank { "#111111" })
|
|
419
|
+
putExtra(NativeImageCropperActivity.EXTRA_STATUS_BAR_COLOR, statusBarColor.ifBlank { "#FFFFFF" })
|
|
420
|
+
putExtra(NativeImageCropperActivity.EXTRA_IS_DARK_THEME, isDarkTheme)
|
|
421
|
+
putExtra(NativeImageCropperActivity.EXTRA_DRAW_UNDER_STATUS_BAR, drawUnderStatusBar)
|
|
422
|
+
putExtra(NativeImageCropperActivity.EXTRA_STATUS_BAR_STYLE, statusBarStyle)
|
|
423
|
+
|
|
424
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_BACKGROUND_COLOR, headerBackgroundColor)
|
|
425
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_TITLE_COLOR, headerTitleColor)
|
|
426
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_TITLE_FONT_SIZE, headerTitleFontSize)
|
|
427
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_TITLE_FONT_FAMILY, headerTitleFontFamily)
|
|
428
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_HEIGHT, headerHeight)
|
|
429
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_PADDING_HORIZONTAL, headerPaddingHorizontal)
|
|
430
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_PADDING_TOP, headerPaddingTop)
|
|
431
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_PADDING_BOTTOM, headerPaddingBottom)
|
|
432
|
+
putExtra(NativeImageCropperActivity.EXTRA_HEADER_ALIGNMENT, headerAlignment)
|
|
433
|
+
|
|
434
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_BACKGROUND_COLOR, buttonContainerBackgroundColor)
|
|
435
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_PADDING_HORIZONTAL, buttonContainerPaddingHorizontal)
|
|
436
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_PADDING_TOP, buttonContainerPaddingTop)
|
|
437
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_PADDING_BOTTOM, buttonContainerPaddingBottom)
|
|
438
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_BUTTON_GAP, buttonGap)
|
|
439
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_BUTTON_HEIGHT, buttonHeight)
|
|
440
|
+
putExtra(NativeImageCropperActivity.EXTRA_BOTTOM_BUTTON_LAYOUT, buttonLayout)
|
|
441
|
+
putExtra(NativeImageCropperActivity.EXTRA_CONTROLS_PLACEMENT, controlsPlacement)
|
|
442
|
+
putExtra(NativeImageCropperActivity.EXTRA_TOP_LEFT_CONTROL, topLeftControl)
|
|
443
|
+
putExtra(NativeImageCropperActivity.EXTRA_TOP_RIGHT_CONTROL, topRightControl)
|
|
444
|
+
putExtra(NativeImageCropperActivity.EXTRA_FOOTER_BUTTON_ORDER, footerButtonOrder)
|
|
445
|
+
|
|
446
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BACKGROUND_COLOR, cancelButtonBackgroundColor)
|
|
447
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BORDER_COLOR, cancelButtonBorderColor)
|
|
448
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BORDER_WIDTH, cancelButtonBorderWidth)
|
|
449
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_FONT_SIZE, cancelButtonFontSize)
|
|
450
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_FONT_FAMILY, cancelButtonFontFamily)
|
|
451
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BORDER_RADIUS, cancelButtonRadius)
|
|
452
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_CONTENT, cancelButtonContent)
|
|
453
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_ICON_URI, cancelButtonIconUri)
|
|
454
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_ICON_BASE64, cancelButtonIconBase64)
|
|
455
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_ICON_TINT, cancelButtonIconTintColor)
|
|
456
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_ICON_SIZE, cancelButtonIconSize)
|
|
457
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_ICON_GAP, cancelButtonIconGap)
|
|
458
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_PADDING_HORIZONTAL, cancelButtonPaddingHorizontal)
|
|
459
|
+
putExtra(NativeImageCropperActivity.EXTRA_CANCEL_BUTTON_PADDING_VERTICAL, cancelButtonPaddingVertical)
|
|
460
|
+
|
|
461
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_TEXT_COLOR, uploadButtonTextColor)
|
|
462
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_COLOR, uploadButtonBackgroundColor)
|
|
463
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BORDER_COLOR, uploadButtonBorderColor)
|
|
464
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BORDER_WIDTH, uploadButtonBorderWidth)
|
|
465
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_FONT_SIZE, uploadButtonFontSize)
|
|
466
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_FONT_FAMILY, uploadButtonFontFamily)
|
|
467
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BORDER_RADIUS, uploadButtonRadius)
|
|
468
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_CONTENT, uploadButtonContent)
|
|
469
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_ICON_URI, uploadButtonIconUri)
|
|
470
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_ICON_BASE64, uploadButtonIconBase64)
|
|
471
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_ICON_TINT, uploadButtonIconTintColor)
|
|
472
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_ICON_SIZE, uploadButtonIconSize)
|
|
473
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_ICON_GAP, uploadButtonIconGap)
|
|
474
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_PADDING_HORIZONTAL, uploadButtonPaddingHorizontal)
|
|
475
|
+
putExtra(NativeImageCropperActivity.EXTRA_UPLOAD_BUTTON_PADDING_VERTICAL, uploadButtonPaddingVertical)
|
|
476
|
+
|
|
477
|
+
putExtra(NativeImageCropperActivity.EXTRA_CROP_GRID_ENABLED, cropGridEnabled)
|
|
478
|
+
putExtra(NativeImageCropperActivity.EXTRA_CROP_FRAME_COLOR, cropFrameColor)
|
|
479
|
+
putExtra(NativeImageCropperActivity.EXTRA_CROP_GRID_COLOR, cropGridColor)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (cropIntent.resolveActivity(activity.packageManager) == null) {
|
|
483
|
+
rejectPending(E_NO_APP_AVAILABLE, "Cropper activity is not available")
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
activity.startActivityForResult(cropIntent, UCrop.REQUEST_CROP)
|
|
487
|
+
activity.overridePendingTransition(0, 0)
|
|
488
|
+
} catch (error: Exception) {
|
|
489
|
+
rejectPending(E_PICKER_ERROR, error.message ?: "Unable to start cropper")
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private fun launchCamera(activity: Activity) {
|
|
494
|
+
val granted =
|
|
495
|
+
ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) ==
|
|
496
|
+
android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
497
|
+
if (!granted) {
|
|
498
|
+
rejectPending(E_PERMISSION_MISSING, "Camera permission is not granted")
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
val outputFile = File(activity.cacheDir, "native-camera-${UUID.randomUUID()}.jpg")
|
|
502
|
+
val authority = activity.packageName + ".nativeimagepicker.provider"
|
|
503
|
+
val outputUri = FileProvider.getUriForFile(activity, authority, outputFile)
|
|
504
|
+
pendingCameraUri = outputUri
|
|
505
|
+
|
|
506
|
+
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
|
|
507
|
+
putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
|
|
508
|
+
putExtra("android.intent.extra.quickCapture", true)
|
|
509
|
+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
510
|
+
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
511
|
+
}
|
|
512
|
+
val cameraHandlers = activity.packageManager.queryIntentActivities(cameraIntent, 0)
|
|
513
|
+
if (cameraHandlers.isNullOrEmpty()) {
|
|
514
|
+
pendingCameraUri = null
|
|
515
|
+
rejectPending(E_NO_APP_AVAILABLE, "No camera app available")
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
for (handler in cameraHandlers) {
|
|
519
|
+
activity.grantUriPermission(
|
|
520
|
+
handler.activityInfo.packageName,
|
|
521
|
+
outputUri,
|
|
522
|
+
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
activity.startActivityForResult(cameraIntent, REQUEST_CAPTURE_IMAGE)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private fun launchSystemPhotoPicker(activity: Activity) {
|
|
529
|
+
val pickRequest = PickVisualMediaRequest.Builder()
|
|
530
|
+
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly)
|
|
531
|
+
.build()
|
|
532
|
+
val pickerIntent = ActivityResultContracts.PickVisualMedia().createIntent(activity, pickRequest).apply {
|
|
533
|
+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
534
|
+
}
|
|
535
|
+
if (pickerIntent.resolveActivity(activity.packageManager) == null) {
|
|
536
|
+
rejectPending(E_NO_APP_AVAILABLE, "No gallery app available")
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
activity.startActivityForResult(pickerIntent, REQUEST_PICK_IMAGE)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private fun handleCropResult(resultCode: Int, data: Intent?) {
|
|
543
|
+
if (pendingPromise == null) return
|
|
544
|
+
if (resultCode == Activity.RESULT_CANCELED) {
|
|
545
|
+
rejectPending(E_PICKER_CANCELLED, E_PICKER_CANCELLED_MSG)
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
val resultUri = UCrop.getOutput(data ?: return rejectPending(E_NO_IMAGE_DATA_FOUND, "Cannot find image data"))
|
|
549
|
+
if (resultCode != Activity.RESULT_OK || resultUri == null) {
|
|
550
|
+
rejectPending(E_NO_IMAGE_DATA_FOUND, "Cannot find image data")
|
|
551
|
+
return
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
encodeExecutor.execute {
|
|
555
|
+
try {
|
|
556
|
+
if (isInvalidated || pendingPromise == null) return@execute
|
|
557
|
+
val result = WritableNativeMap().apply {
|
|
558
|
+
putString("path", resultUri.toString())
|
|
559
|
+
if (includeBase64) {
|
|
560
|
+
val encoded = toBase64String(resultUri)
|
|
561
|
+
if (encoded.isNullOrBlank()) {
|
|
562
|
+
rejectPending(E_NO_IMAGE_DATA_FOUND, "Cannot encode selected image")
|
|
563
|
+
return@execute
|
|
564
|
+
}
|
|
565
|
+
putString("base64", encoded)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
resolvePending(result)
|
|
569
|
+
} catch (error: Exception) {
|
|
570
|
+
rejectPending(E_PICKER_ERROR, error.message ?: "Unable to process image")
|
|
571
|
+
} finally {
|
|
572
|
+
runCatching {
|
|
573
|
+
if ("file".equals(resultUri.scheme, ignoreCase = true)) {
|
|
574
|
+
File(resultUri.path.orEmpty()).delete()
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private fun parseColor(color: String, fallback: Int): Int {
|
|
582
|
+
return runCatching { Color.parseColor(color) }.getOrElse { fallback }
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private fun toBase64String(uri: Uri): String? {
|
|
586
|
+
emitProgress(0.0)
|
|
587
|
+
val output = ByteArrayOutputStream()
|
|
588
|
+
Base64OutputStream(output, Base64.NO_WRAP).use { base64Stream ->
|
|
589
|
+
val resolver = reactContext.contentResolver
|
|
590
|
+
val total = runCatching {
|
|
591
|
+
resolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
|
|
592
|
+
val len = afd.length
|
|
593
|
+
if (len > 0) len else -1L
|
|
594
|
+
} ?: -1L
|
|
595
|
+
}.getOrDefault(-1L)
|
|
596
|
+
var readSoFar = 0L
|
|
597
|
+
var lastEmitted = 0.0
|
|
598
|
+
resolver.openInputStream(uri)?.use { input ->
|
|
599
|
+
val buffer = ByteArray(16 * 1024)
|
|
600
|
+
while (true) {
|
|
601
|
+
val read = input.read(buffer)
|
|
602
|
+
if (read <= 0) break
|
|
603
|
+
base64Stream.write(buffer, 0, read)
|
|
604
|
+
if (total > 0) {
|
|
605
|
+
readSoFar += read.toLong()
|
|
606
|
+
val p = (readSoFar.toDouble() / total.toDouble()).coerceIn(0.0, 1.0)
|
|
607
|
+
// Emit only if progress moved enough to matter.
|
|
608
|
+
if (p - lastEmitted >= 0.03) {
|
|
609
|
+
lastEmitted = p
|
|
610
|
+
emitProgress(p)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
} ?: return null
|
|
615
|
+
}
|
|
616
|
+
emitProgress(1.0)
|
|
617
|
+
return output.toString(Charsets.UTF_8.name())
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private fun emitProgress(progress: Double) {
|
|
621
|
+
if (isInvalidated) return
|
|
622
|
+
val map = WritableNativeMap().apply {
|
|
623
|
+
putDouble("progress", progress.coerceIn(0.0, 1.0))
|
|
624
|
+
}
|
|
625
|
+
reactContext
|
|
626
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
627
|
+
.emit("NativeImageCropperProgress", map)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private fun cleanupCameraUriPermissions(activity: Activity) {
|
|
631
|
+
val uri = pendingCameraUri ?: return
|
|
632
|
+
runCatching {
|
|
633
|
+
activity.revokeUriPermission(
|
|
634
|
+
uri,
|
|
635
|
+
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private fun resolvePending(data: WritableNativeMap) {
|
|
641
|
+
val promise = pendingPromise ?: return
|
|
642
|
+
pendingPromise = null
|
|
643
|
+
reactContext.runOnUiQueueThread { promise.resolve(data) }
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private fun rejectPending(code: String, message: String) {
|
|
647
|
+
val promise = pendingPromise ?: return
|
|
648
|
+
pendingPromise = null
|
|
649
|
+
reactContext.runOnUiQueueThread { promise.reject(code, message) }
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private fun ReadableMap.getIntOrDefault(key: String, fallback: Int): Int {
|
|
653
|
+
if (!hasKey(key) || isNull(key)) return fallback
|
|
654
|
+
return runCatching { getInt(key) }.getOrDefault(fallback)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private fun ReadableMap.getStringOrDefault(key: String, fallback: String): String {
|
|
658
|
+
if (!hasKey(key) || isNull(key)) return fallback
|
|
659
|
+
return getString(key).orEmpty().ifBlank { fallback }
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
private fun sanitizePositive(value: Int, fallback: Int): Int {
|
|
663
|
+
return if (value > 0) value else fallback
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
private fun sanitizeNonNegative(value: Int, fallback: Int): Int {
|
|
667
|
+
return if (value >= 0) value else fallback
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private fun getOptionalMap(map: ReadableMap, key: String): ReadableMap? {
|
|
671
|
+
return if (map.hasKey(key) && !map.isNull(key)) map.getMap(key) else null
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private fun getMapString(map: ReadableMap?, key: String, fallback: String): String {
|
|
675
|
+
if (map == null || !map.hasKey(key) || map.isNull(key)) return fallback
|
|
676
|
+
return map.getString(key).orEmpty().ifBlank { fallback }
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
private fun getMapInt(map: ReadableMap?, key: String, fallback: Int): Int {
|
|
680
|
+
if (map == null || !map.hasKey(key) || map.isNull(key)) return fallback
|
|
681
|
+
return runCatching { map.getInt(key) }.getOrDefault(fallback)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|