react-native-picture-selector 1.0.0
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 +627 -0
- package/android/CMakeLists.txt +30 -0
- package/android/build.gradle +79 -0
- package/android/proguard-rules.pro +21 -0
- package/android/src/main/AndroidManifest.xml +39 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/GlideEngine.kt +80 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/HybridPictureSelector.kt +138 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/LubanCompressEngine.kt +58 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/MediaAssetMapper.kt +69 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/NitroPictureSelectorPackage.kt +52 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/PictureSelectorOptionsMapper.kt +105 -0
- package/android/src/main/kotlin/com/margelo/pictureselector/UCropEngine.kt +57 -0
- package/android/src/main/res/xml/file_paths.xml +8 -0
- package/ios/HybridPictureSelector.swift +386 -0
- package/ios/NitroPictureSelector.podspec +39 -0
- package/lib/commonjs/PictureSelector.js +74 -0
- package/lib/commonjs/PictureSelector.js.map +1 -0
- package/lib/commonjs/index.js +39 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/PictureSelector.nitro.js +34 -0
- package/lib/commonjs/specs/PictureSelector.nitro.js.map +1 -0
- package/lib/commonjs/types.js +44 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/usePictureSelector.js +122 -0
- package/lib/commonjs/usePictureSelector.js.map +1 -0
- package/lib/module/PictureSelector.js +71 -0
- package/lib/module/PictureSelector.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/PictureSelector.nitro.js +36 -0
- package/lib/module/specs/PictureSelector.nitro.js.map +1 -0
- package/lib/module/types.js +29 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/usePictureSelector.js +119 -0
- package/lib/module/usePictureSelector.js.map +1 -0
- package/lib/typescript/PictureSelector.d.ts +23 -0
- package/lib/typescript/PictureSelector.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/specs/PictureSelector.nitro.d.ts +96 -0
- package/lib/typescript/specs/PictureSelector.nitro.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +16 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/usePictureSelector.d.ts +26 -0
- package/lib/typescript/usePictureSelector.d.ts.map +1 -0
- package/nitro.json +11 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroPictureSelector+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroPictureSelector+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroPictureSelectorOnLoad.cpp +41 -0
- package/nitrogen/generated/android/NitroPictureSelectorOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JCompressOptions.hpp +69 -0
- package/nitrogen/generated/android/c++/JCropOptions.hpp +73 -0
- package/nitrogen/generated/android/c++/JHybridHybridPictureSelectorSpec.cpp +125 -0
- package/nitrogen/generated/android/c++/JHybridHybridPictureSelectorSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JMediaAsset.hpp +98 -0
- package/nitrogen/generated/android/c++/JMediaType.hpp +61 -0
- package/nitrogen/generated/android/c++/JPickerTheme.hpp +64 -0
- package/nitrogen/generated/android/c++/JPictureSelectorOptions.hpp +121 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/CompressOptions.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/CropOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/HybridHybridPictureSelectorSpec.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/MediaAsset.kt +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/MediaType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/NitroPictureSelectorOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/PickerTheme.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/PictureSelectorOptions.kt +65 -0
- package/nitrogen/generated/ios/NitroPictureSelector+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroPictureSelector-Swift-Cxx-Bridge.cpp +49 -0
- package/nitrogen/generated/ios/NitroPictureSelector-Swift-Cxx-Bridge.hpp +270 -0
- package/nitrogen/generated/ios/NitroPictureSelector-Swift-Cxx-Umbrella.hpp +65 -0
- package/nitrogen/generated/ios/c++/HybridHybridPictureSelectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridHybridPictureSelectorSpecSwift.hpp +110 -0
- package/nitrogen/generated/ios/swift/CompressOptions.swift +83 -0
- package/nitrogen/generated/ios/swift/CropOptions.swift +101 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_MediaAsset_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridHybridPictureSelectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridHybridPictureSelectorSpec_cxx.swift +176 -0
- package/nitrogen/generated/ios/swift/MediaAsset.swift +118 -0
- package/nitrogen/generated/ios/swift/MediaType.swift +44 -0
- package/nitrogen/generated/ios/swift/PickerTheme.swift +48 -0
- package/nitrogen/generated/ios/swift/PictureSelectorOptions.swift +182 -0
- package/nitrogen/generated/shared/c++/CompressOptions.hpp +95 -0
- package/nitrogen/generated/shared/c++/CropOptions.hpp +99 -0
- package/nitrogen/generated/shared/c++/HybridHybridPictureSelectorSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridHybridPictureSelectorSpec.hpp +69 -0
- package/nitrogen/generated/shared/c++/MediaAsset.hpp +124 -0
- package/nitrogen/generated/shared/c++/MediaType.hpp +80 -0
- package/nitrogen/generated/shared/c++/PickerTheme.hpp +84 -0
- package/nitrogen/generated/shared/c++/PictureSelectorOptions.hpp +132 -0
- package/package.json +76 -0
- package/src/PictureSelector.ts +72 -0
- package/src/index.ts +16 -0
- package/src/specs/PictureSelector.nitro.ts +121 -0
- package/src/types.ts +38 -0
- package/src/usePictureSelector.ts +102 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.kotlin_version = '1.9.22'
|
|
3
|
+
repositories {
|
|
4
|
+
google()
|
|
5
|
+
mavenCentral()
|
|
6
|
+
}
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath 'com.android.tools.build:gradle:8.1.4'
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
apply plugin: 'com.android.library'
|
|
14
|
+
apply plugin: 'kotlin-android'
|
|
15
|
+
|
|
16
|
+
android {
|
|
17
|
+
namespace 'com.margelo.pictureselector'
|
|
18
|
+
compileSdk 34
|
|
19
|
+
|
|
20
|
+
defaultConfig {
|
|
21
|
+
minSdk 24
|
|
22
|
+
targetSdk 34
|
|
23
|
+
ndk {
|
|
24
|
+
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
|
25
|
+
}
|
|
26
|
+
consumerProguardFiles 'proguard-rules.pro'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
buildFeatures {
|
|
30
|
+
prefab true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
compileOptions {
|
|
34
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
35
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
kotlinOptions {
|
|
39
|
+
jvmTarget = '17'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sourceSets {
|
|
43
|
+
main {
|
|
44
|
+
// Include nitrogen-generated Kotlin sources
|
|
45
|
+
java.srcDirs += [
|
|
46
|
+
'src/main/kotlin',
|
|
47
|
+
'../nitrogen/generated/android/kotlin'
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
repositories {
|
|
54
|
+
google()
|
|
55
|
+
mavenCentral()
|
|
56
|
+
maven { url 'https://jitpack.io' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dependencies {
|
|
60
|
+
implementation 'com.facebook.react:react-native'
|
|
61
|
+
|
|
62
|
+
// Coroutines
|
|
63
|
+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
|
64
|
+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
|
|
65
|
+
|
|
66
|
+
// ─── PictureSelector v3.11.2 ─────────────────────────────────────────────
|
|
67
|
+
implementation 'io.github.lucksiege:pictureselector:v3.11.2'
|
|
68
|
+
implementation 'io.github.lucksiege:compress:v3.11.2'
|
|
69
|
+
implementation 'io.github.lucksiege:ucrop:v3.11.2'
|
|
70
|
+
implementation 'io.github.lucksiege:camerax:v3.11.2'
|
|
71
|
+
|
|
72
|
+
// ─── Image loading engine (required by PictureSelector) ──────────────────
|
|
73
|
+
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
|
74
|
+
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
|
75
|
+
|
|
76
|
+
// ─── Nitro bridge (resolved at app level via peer dependency) ────────────
|
|
77
|
+
// react-native-nitro-modules is a peerDependency; the C++ prefab is
|
|
78
|
+
// provided by the consuming app's node_modules.
|
|
79
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# PictureSelector
|
|
2
|
+
-keep class com.luck.picture.lib.** { *; }
|
|
3
|
+
-keep class com.luck.lib.camerax.** { *; }
|
|
4
|
+
|
|
5
|
+
# uCrop
|
|
6
|
+
-dontwarn com.yalantis.ucrop**
|
|
7
|
+
-keep class com.yalantis.ucrop** { *; }
|
|
8
|
+
-keep interface com.yalantis.ucrop** { *; }
|
|
9
|
+
|
|
10
|
+
# Glide
|
|
11
|
+
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|
12
|
+
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
|
13
|
+
<init>(...);
|
|
14
|
+
}
|
|
15
|
+
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
|
16
|
+
**[] $VALUES;
|
|
17
|
+
public *;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Nitro bridge
|
|
21
|
+
-keep class com.margelo.pictureselector.** { *; }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
|
|
4
|
+
<!-- ─── Permissions ───────────────────────────────────────────────────── -->
|
|
5
|
+
|
|
6
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
7
|
+
|
|
8
|
+
<!-- Android ≤ 12 (API 32) -->
|
|
9
|
+
<uses-permission
|
|
10
|
+
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
11
|
+
android:maxSdkVersion="32" />
|
|
12
|
+
|
|
13
|
+
<!-- Android 13+ (API 33+) granular media permissions -->
|
|
14
|
+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
15
|
+
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
16
|
+
|
|
17
|
+
<!-- Required to write camera captures on Android ≤ 9 (API 28) -->
|
|
18
|
+
<uses-permission
|
|
19
|
+
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
20
|
+
android:maxSdkVersion="28" />
|
|
21
|
+
|
|
22
|
+
<application>
|
|
23
|
+
<!--
|
|
24
|
+
FileProvider is required by PictureSelector for sharing URIs on
|
|
25
|
+
Android 7+ (API 24+). The authority must match the one used in
|
|
26
|
+
PictureSelectorOptionsMapper (${applicationId}.fileprovider).
|
|
27
|
+
-->
|
|
28
|
+
<provider
|
|
29
|
+
android:name="androidx.core.content.FileProvider"
|
|
30
|
+
android:authorities="${applicationId}.fileprovider"
|
|
31
|
+
android:exported="false"
|
|
32
|
+
android:grantUriPermissions="true">
|
|
33
|
+
<meta-data
|
|
34
|
+
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
35
|
+
android:resource="@xml/file_paths" />
|
|
36
|
+
</provider>
|
|
37
|
+
</application>
|
|
38
|
+
|
|
39
|
+
</manifest>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
package com.margelo.pictureselector
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.widget.ImageView
|
|
5
|
+
import com.bumptech.glide.Glide
|
|
6
|
+
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
7
|
+
import com.luck.picture.lib.engine.ImageEngine
|
|
8
|
+
import com.luck.picture.lib.utils.ActivityCompatHelper
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Glide-based ImageEngine required by PictureSelector for displaying
|
|
12
|
+
* thumbnails in the gallery grid and album cover.
|
|
13
|
+
*/
|
|
14
|
+
class GlideEngine private constructor() : ImageEngine {
|
|
15
|
+
|
|
16
|
+
override fun loadImage(context: Context, url: String, imageView: ImageView) {
|
|
17
|
+
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
|
18
|
+
Glide.with(context)
|
|
19
|
+
.load(url)
|
|
20
|
+
.override(180, 180)
|
|
21
|
+
.centerCrop()
|
|
22
|
+
.into(imageView)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override fun loadImage(
|
|
26
|
+
context: Context,
|
|
27
|
+
imageView: ImageView,
|
|
28
|
+
url: String,
|
|
29
|
+
maxWidth: Int,
|
|
30
|
+
maxHeight: Int,
|
|
31
|
+
) {
|
|
32
|
+
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
|
33
|
+
Glide.with(context)
|
|
34
|
+
.load(url)
|
|
35
|
+
.override(maxWidth, maxHeight)
|
|
36
|
+
.centerCrop()
|
|
37
|
+
.into(imageView)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun loadAlbumCoverImage(context: Context, url: String, imageView: ImageView) {
|
|
41
|
+
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
|
42
|
+
Glide.with(context)
|
|
43
|
+
.asBitmap()
|
|
44
|
+
.load(url)
|
|
45
|
+
.override(180, 180)
|
|
46
|
+
.centerCrop()
|
|
47
|
+
.sizeMultiplier(0.5f)
|
|
48
|
+
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
|
49
|
+
.into(imageView)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun loadGridImage(context: Context, url: String, imageView: ImageView) {
|
|
53
|
+
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
|
54
|
+
Glide.with(context)
|
|
55
|
+
.load(url)
|
|
56
|
+
.override(200, 200)
|
|
57
|
+
.centerCrop()
|
|
58
|
+
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
|
59
|
+
.into(imageView)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun pauseRequests(context: Context) {
|
|
63
|
+
Glide.with(context).pauseRequests()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun resumeRequests(context: Context) {
|
|
67
|
+
Glide.with(context).resumeRequests()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
companion object {
|
|
71
|
+
@Volatile
|
|
72
|
+
private var instance: GlideEngine? = null
|
|
73
|
+
|
|
74
|
+
@JvmStatic
|
|
75
|
+
fun createGlideEngine(): GlideEngine =
|
|
76
|
+
instance ?: synchronized(this) {
|
|
77
|
+
instance ?: GlideEngine().also { instance = it }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package com.margelo.pictureselector
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.luck.picture.lib.PictureSelector
|
|
6
|
+
import com.luck.picture.lib.config.SelectMimeType
|
|
7
|
+
import com.luck.picture.lib.entity.LocalMedia
|
|
8
|
+
import com.luck.picture.lib.interfaces.OnResultCallbackListener
|
|
9
|
+
import com.margelo.nitro.core.Promise
|
|
10
|
+
import com.margelo.nitro.com.margelo.pictureselector.HybridHybridPictureSelectorSpec
|
|
11
|
+
import com.margelo.nitro.com.margelo.pictureselector.MediaAsset
|
|
12
|
+
import com.margelo.nitro.com.margelo.pictureselector.MediaType
|
|
13
|
+
import com.margelo.nitro.com.margelo.pictureselector.PictureSelectorOptions
|
|
14
|
+
import kotlinx.coroutines.Dispatchers
|
|
15
|
+
import kotlinx.coroutines.withContext
|
|
16
|
+
import kotlin.coroutines.resume
|
|
17
|
+
import kotlin.coroutines.resumeWithException
|
|
18
|
+
import kotlin.coroutines.suspendCoroutine
|
|
19
|
+
|
|
20
|
+
// ─── Nitrogen-generated types ─────────────────────────────────────────────────
|
|
21
|
+
// Generated by nitrogen into:
|
|
22
|
+
// nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/
|
|
23
|
+
// HybridHybridPictureSelectorSpec.kt — abstract base class (extends HybridObject)
|
|
24
|
+
// MediaAsset.kt — data class
|
|
25
|
+
// PictureSelectorOptions.kt — data class
|
|
26
|
+
// CropOptions.kt — data class
|
|
27
|
+
// CompressOptions.kt — data class
|
|
28
|
+
// MediaType.kt — enum class
|
|
29
|
+
// PickerTheme.kt — enum class
|
|
30
|
+
//
|
|
31
|
+
// Run `npm run generate` before building.
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Nitro HybridObject implementation for the Android side.
|
|
35
|
+
*
|
|
36
|
+
* Extends the nitrogen-generated [HybridHybridPictureSelectorSpec] which provides
|
|
37
|
+
* the JSI bridge. The [reactContext] is injected by [NitroPictureSelectorPackage].
|
|
38
|
+
*
|
|
39
|
+
* Threading contract:
|
|
40
|
+
* - [openPicker] and [openCamera] are called on the JS thread.
|
|
41
|
+
* - PictureSelector.create(...).openGallery() must be called on the Main thread.
|
|
42
|
+
* - We switch to Main via [withContext(Dispatchers.Main)] inside Promise.async.
|
|
43
|
+
*
|
|
44
|
+
* API REQUIRES VERIFICATION:
|
|
45
|
+
* - Confirm Promise.async accepts a CoroutineContext parameter in the version
|
|
46
|
+
* of react-native-nitro-modules used.
|
|
47
|
+
*/
|
|
48
|
+
class HybridPictureSelector(
|
|
49
|
+
private val reactContext: ReactApplicationContext,
|
|
50
|
+
) : HybridHybridPictureSelectorSpec() {
|
|
51
|
+
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
53
|
+
// Public API
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
override fun openPicker(options: PictureSelectorOptions): Promise<Array<MediaAsset>> {
|
|
57
|
+
return Promise.async {
|
|
58
|
+
withContext(Dispatchers.Main) {
|
|
59
|
+
val activity = requireActivity()
|
|
60
|
+
val mimeType = PictureSelectorOptionsMapper.toSelectMimeType(options.mediaType)
|
|
61
|
+
|
|
62
|
+
suspendCoroutine { cont ->
|
|
63
|
+
PictureSelector.create(activity)
|
|
64
|
+
.openGallery(mimeType)
|
|
65
|
+
.setImageEngine(GlideEngine.createGlideEngine())
|
|
66
|
+
.also { builder ->
|
|
67
|
+
PictureSelectorOptionsMapper.applyGallery(builder, options)
|
|
68
|
+
}
|
|
69
|
+
.forResult(object : OnResultCallbackListener<LocalMedia>() {
|
|
70
|
+
override fun onResult(result: ArrayList<LocalMedia>) {
|
|
71
|
+
cont.resume(MediaAssetMapper.map(result))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override fun onCancel() {
|
|
75
|
+
cont.resumeWithException(
|
|
76
|
+
PictureSelectorException("CANCELLED", "User cancelled the picker")
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun openCamera(options: PictureSelectorOptions): Promise<Array<MediaAsset>> {
|
|
86
|
+
return Promise.async {
|
|
87
|
+
withContext(Dispatchers.Main) {
|
|
88
|
+
val activity = requireActivity()
|
|
89
|
+
// Camera always captures a single item; force image if mediaType == ALL
|
|
90
|
+
val mimeType = when (options.mediaType) {
|
|
91
|
+
MediaType.VIDEO -> SelectMimeType.ofVideo()
|
|
92
|
+
else -> SelectMimeType.ofImage()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
suspendCoroutine { cont ->
|
|
96
|
+
PictureSelector.create(activity)
|
|
97
|
+
.openCamera(mimeType)
|
|
98
|
+
.setImageEngine(GlideEngine.createGlideEngine())
|
|
99
|
+
.also { builder ->
|
|
100
|
+
PictureSelectorOptionsMapper.applyCamera(builder, options)
|
|
101
|
+
}
|
|
102
|
+
.forResult(object : OnResultCallbackListener<LocalMedia>() {
|
|
103
|
+
override fun onResult(result: ArrayList<LocalMedia>) {
|
|
104
|
+
cont.resume(MediaAssetMapper.map(result))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override fun onCancel() {
|
|
108
|
+
cont.resumeWithException(
|
|
109
|
+
PictureSelectorException("CANCELLED", "User cancelled the camera")
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
119
|
+
// Helpers
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
private fun requireActivity(): Activity {
|
|
123
|
+
return reactContext.currentActivity
|
|
124
|
+
?: throw PictureSelectorException(
|
|
125
|
+
"UNKNOWN",
|
|
126
|
+
"No current Activity available. Ensure the picker is called from a mounted component."
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
132
|
+
// Typed exception — message becomes the JS Error.message
|
|
133
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
class PictureSelectorException(
|
|
136
|
+
val code: String,
|
|
137
|
+
message: String,
|
|
138
|
+
) : Exception(message)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package com.margelo.pictureselector
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import com.luck.picture.lib.engine.CompressFileEngine
|
|
6
|
+
import com.luck.picture.lib.interfaces.OnKeyValueResultCallbackListener
|
|
7
|
+
import top.zibin.luban.Luban
|
|
8
|
+
import top.zibin.luban.OnNewCompressListener
|
|
9
|
+
import java.io.File
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Luban-based compression engine for PictureSelector v3.
|
|
13
|
+
*
|
|
14
|
+
* PictureSelector bundles Luban via its compress artifact
|
|
15
|
+
* (io.github.lucksiege:compress). This engine is invoked after selection
|
|
16
|
+
* when compression is enabled.
|
|
17
|
+
*
|
|
18
|
+
* API REQUIRES VERIFICATION: The exact Luban API (OnNewCompressListener
|
|
19
|
+
* callback method signatures) should be confirmed against the bundled
|
|
20
|
+
* Luban version in io.github.lucksiege:compress:v3.11.2.
|
|
21
|
+
*/
|
|
22
|
+
class LubanCompressEngine(
|
|
23
|
+
private val quality: Int,
|
|
24
|
+
private val maxWidth: Int,
|
|
25
|
+
private val maxHeight: Int,
|
|
26
|
+
) : CompressFileEngine {
|
|
27
|
+
|
|
28
|
+
override fun onStartCompress(
|
|
29
|
+
context: Context,
|
|
30
|
+
source: ArrayList<Uri>,
|
|
31
|
+
call: OnKeyValueResultCallbackListener,
|
|
32
|
+
) {
|
|
33
|
+
// API REQUIRES VERIFICATION: setQuality() method name in bundled Luban version.
|
|
34
|
+
// In most Luban forks bundled with PictureSelector, quality is set via .quality(Int)
|
|
35
|
+
// or .setCompressQuality(Int). Adjust the method name after verifying against
|
|
36
|
+
// io.github.lucksiege:compress:v3.11.2 source.
|
|
37
|
+
Luban.with(context)
|
|
38
|
+
.load(source)
|
|
39
|
+
.ignoreBy(100) // skip files under 100 KB
|
|
40
|
+
.setTargetDir(context.cacheDir.absolutePath)
|
|
41
|
+
.quality(quality) // 0–100 JPEG quality
|
|
42
|
+
.setCompressListener(object : OnNewCompressListener {
|
|
43
|
+
override fun onStart() {
|
|
44
|
+
// no-op
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override fun onSuccess(source: String, compressFile: File) {
|
|
48
|
+
call.onCallback(source, compressFile.absolutePath)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun onError(source: String, e: Throwable) {
|
|
52
|
+
// Return null to signal failure; PictureSelector will use original
|
|
53
|
+
call.onCallback(source, null)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.launch()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package com.margelo.pictureselector
|
|
2
|
+
|
|
3
|
+
import com.luck.picture.lib.entity.LocalMedia
|
|
4
|
+
import com.margelo.nitro.com.margelo.pictureselector.MediaAsset
|
|
5
|
+
import java.io.File
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maps PictureSelector [LocalMedia] results to the Nitro bridge [MediaAsset].
|
|
9
|
+
*
|
|
10
|
+
* Priority for the final URI:
|
|
11
|
+
* 1. Compressed file path (isCompressed && compressPath != null)
|
|
12
|
+
* 2. Cropped file path (isCut && cutPath != null)
|
|
13
|
+
* 3. Real file path (realPath != null)
|
|
14
|
+
* 4. Fallback (path — may be content:// URI)
|
|
15
|
+
*
|
|
16
|
+
* API REQUIRES VERIFICATION:
|
|
17
|
+
* - LocalMedia.bucketDisplayName field name in v3.11.2
|
|
18
|
+
* (may be bucketDisplayName or albumName).
|
|
19
|
+
* - LocalMedia.size field name (may be size or fileSize).
|
|
20
|
+
* - LocalMedia.duration unit (ms in v3; confirm).
|
|
21
|
+
*/
|
|
22
|
+
object MediaAssetMapper {
|
|
23
|
+
|
|
24
|
+
@JvmStatic
|
|
25
|
+
fun map(results: ArrayList<LocalMedia>): Array<MediaAsset> {
|
|
26
|
+
return results.map { media -> mapOne(media) }.toTypedArray()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private fun mapOne(media: LocalMedia): MediaAsset {
|
|
30
|
+
val finalPath = resolveFilePath(media)
|
|
31
|
+
val finalUri = if (finalPath.startsWith("content://")) finalPath
|
|
32
|
+
else "file://$finalPath"
|
|
33
|
+
|
|
34
|
+
val editedPath = when {
|
|
35
|
+
media.isCut && media.cutPath != null -> "file://${media.cutPath}"
|
|
36
|
+
else -> null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
val type = if (media.mimeType?.startsWith("video") == true) "video" else "image"
|
|
40
|
+
val fileName = File(finalPath).name.takeIf { it.isNotEmpty() } ?: "unknown"
|
|
41
|
+
|
|
42
|
+
return MediaAsset(
|
|
43
|
+
uri = finalUri,
|
|
44
|
+
type = type,
|
|
45
|
+
mimeType = media.mimeType ?: "image/jpeg",
|
|
46
|
+
width = media.width.toDouble(),
|
|
47
|
+
height = media.height.toDouble(),
|
|
48
|
+
duration = media.duration.toDouble(), // milliseconds
|
|
49
|
+
fileName = fileName,
|
|
50
|
+
fileSize = media.size.toDouble(), // API REQUIRES VERIFICATION: field name
|
|
51
|
+
editedUri = editedPath,
|
|
52
|
+
isOriginal = null,
|
|
53
|
+
bucketName = media.bucketDisplayName, // API REQUIRES VERIFICATION: field name
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private fun resolveFilePath(media: LocalMedia): String {
|
|
58
|
+
if (media.isCompressed && !media.compressPath.isNullOrEmpty()) {
|
|
59
|
+
return media.compressPath!!
|
|
60
|
+
}
|
|
61
|
+
if (media.isCut && !media.cutPath.isNullOrEmpty()) {
|
|
62
|
+
return media.cutPath!!
|
|
63
|
+
}
|
|
64
|
+
if (!media.realPath.isNullOrEmpty()) {
|
|
65
|
+
return media.realPath!!
|
|
66
|
+
}
|
|
67
|
+
return media.path ?: ""
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.margelo.pictureselector
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import com.margelo.nitro.NitroModules
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* React Native Package that registers the Nitro HybridObject.
|
|
11
|
+
*
|
|
12
|
+
* For new architecture (Turbo Modules / Nitro): this package is discovered
|
|
13
|
+
* automatically via autolinking when the consumer runs
|
|
14
|
+
* `react-native link` or Gradle sync.
|
|
15
|
+
*
|
|
16
|
+
* Manual registration (legacy / non-autolink):
|
|
17
|
+
* ```kotlin
|
|
18
|
+
* // MainApplication.kt
|
|
19
|
+
* override fun getPackages() = PackageList(this).packages + NitroPictureSelectorPackage()
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* API REQUIRES VERIFICATION:
|
|
23
|
+
* - NitroModules.addHybridObjectCreator is the actual registration API in
|
|
24
|
+
* react-native-nitro-modules for Android. Verify against the installed
|
|
25
|
+
* version of the library. The creator name ("PictureSelector") must
|
|
26
|
+
* exactly match the string passed to NitroModules.createHybridObject()
|
|
27
|
+
* on the JS side.
|
|
28
|
+
*/
|
|
29
|
+
class NitroPictureSelectorPackage : ReactPackage {
|
|
30
|
+
|
|
31
|
+
override fun createNativeModules(
|
|
32
|
+
reactContext: ReactApplicationContext,
|
|
33
|
+
): List<NativeModule> {
|
|
34
|
+
// Register the HybridObject factory. The name must match the JS-side
|
|
35
|
+
// NitroModules.createHybridObject<HybridPictureSelector>('PictureSelector').
|
|
36
|
+
NitroModules.addHybridObjectCreator("PictureSelector") {
|
|
37
|
+
try {
|
|
38
|
+
HybridPictureSelector(reactContext)
|
|
39
|
+
} catch (e: Exception) {
|
|
40
|
+
throw RuntimeException(
|
|
41
|
+
"[react-native-picture-selector] Failed to create HybridPictureSelector: ${e.message}",
|
|
42
|
+
e
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return emptyList()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun createViewManagers(
|
|
50
|
+
reactContext: ReactApplicationContext,
|
|
51
|
+
): List<ViewManager<*, *>> = emptyList()
|
|
52
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package com.margelo.pictureselector
|
|
2
|
+
|
|
3
|
+
import com.luck.picture.lib.config.SelectMimeType
|
|
4
|
+
import com.margelo.nitro.com.margelo.pictureselector.MediaType
|
|
5
|
+
import com.margelo.nitro.com.margelo.pictureselector.PictureSelectorOptions
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maps the JS [PictureSelectorOptions] onto a PictureSelector v3
|
|
9
|
+
* selection model.
|
|
10
|
+
*
|
|
11
|
+
* Note: [PictureSelectionModel] is the builder returned by
|
|
12
|
+
* [PictureSelector.create(activity).openGallery()] /
|
|
13
|
+
* [PictureSelector.create(activity).openCamera()].
|
|
14
|
+
*
|
|
15
|
+
* API REQUIRES VERIFICATION:
|
|
16
|
+
* - setSelectVideoMaxDuration / setSelectVideoMinDuration unit (seconds vs ms).
|
|
17
|
+
* In v3.11.2 these accept seconds. Confirm in the library source.
|
|
18
|
+
* - setSelectorUIStyle builder method name & enum values.
|
|
19
|
+
* - setSelectedData signature for pre-selected items.
|
|
20
|
+
*/
|
|
21
|
+
object PictureSelectorOptionsMapper {
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply gallery-specific options to the builder.
|
|
25
|
+
* Called before .forResult().
|
|
26
|
+
*/
|
|
27
|
+
@JvmStatic
|
|
28
|
+
fun applyGallery(
|
|
29
|
+
builder: com.luck.picture.lib.basic.PictureSelectionModel,
|
|
30
|
+
options: PictureSelectorOptions,
|
|
31
|
+
) {
|
|
32
|
+
applyCommon(builder, options)
|
|
33
|
+
|
|
34
|
+
// Maximum items the user may select
|
|
35
|
+
builder.setMaxSelectNum((options.maxCount ?: 1.0).toInt())
|
|
36
|
+
|
|
37
|
+
// Show camera button inside gallery
|
|
38
|
+
builder.isDisplayCamera(options.enableCamera ?: true)
|
|
39
|
+
|
|
40
|
+
// Pre-selected assets — requires conversion to LocalMedia list
|
|
41
|
+
// API REQUIRES VERIFICATION: setSelectedData(List<LocalMedia>) signature
|
|
42
|
+
// options.selectedAssets is currently unused in v1; add in future.
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Apply camera-specific options to the builder.
|
|
47
|
+
*/
|
|
48
|
+
@JvmStatic
|
|
49
|
+
fun applyCamera(
|
|
50
|
+
builder: com.luck.picture.lib.basic.PictureSelectionModel,
|
|
51
|
+
options: PictureSelectorOptions,
|
|
52
|
+
) {
|
|
53
|
+
applyCommon(builder, options)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Shared options ──────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
private fun applyCommon(
|
|
59
|
+
builder: com.luck.picture.lib.basic.PictureSelectionModel,
|
|
60
|
+
options: PictureSelectorOptions,
|
|
61
|
+
) {
|
|
62
|
+
// ── Video duration limits ─────────────────────────────────────────────
|
|
63
|
+
// PictureSelector v3 accepts duration in seconds.
|
|
64
|
+
options.maxVideoDuration?.let { sec ->
|
|
65
|
+
builder.setSelectVideoMaxDuration(sec.toInt())
|
|
66
|
+
}
|
|
67
|
+
options.minVideoDuration?.let { sec ->
|
|
68
|
+
builder.setSelectVideoMinDuration(sec.toInt())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Crop engine ───────────────────────────────────────────────────────
|
|
72
|
+
val crop = options.crop
|
|
73
|
+
val maxCount = (options.maxCount ?: 1.0).toInt()
|
|
74
|
+
if (crop != null && crop.enabled && maxCount == 1) {
|
|
75
|
+
val freeStyle = crop.freeStyle ?: false
|
|
76
|
+
val circular = crop.circular ?: false
|
|
77
|
+
val ratioX = (crop.ratioX ?: 1.0).toFloat()
|
|
78
|
+
val ratioY = (crop.ratioY ?: 1.0).toFloat()
|
|
79
|
+
// Map 0–1 JS quality to 0–100 JPEG quality
|
|
80
|
+
val quality = (((options.compress?.quality ?: 0.8) * 100).toInt()).coerceIn(10, 100)
|
|
81
|
+
|
|
82
|
+
builder.setCropEngine(UCropEngine(freeStyle, ratioX, ratioY, circular, quality))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Compress engine ───────────────────────────────────────────────────
|
|
86
|
+
val compress = options.compress
|
|
87
|
+
if (compress != null && compress.enabled) {
|
|
88
|
+
val quality = (((compress.quality ?: 0.8) * 100).toInt()).coerceIn(10, 100)
|
|
89
|
+
val maxWidth = (compress.maxWidth ?: 1920.0).toInt()
|
|
90
|
+
val maxHeight = (compress.maxHeight ?: 1920.0).toInt()
|
|
91
|
+
|
|
92
|
+
builder.setCompressEngine(LubanCompressEngine(quality, maxWidth, maxHeight))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert a JS [MediaType] enum to the PictureSelector SelectMimeType constant.
|
|
98
|
+
*/
|
|
99
|
+
@JvmStatic
|
|
100
|
+
fun toSelectMimeType(mediaType: MediaType?): Int = when (mediaType) {
|
|
101
|
+
MediaType.VIDEO -> SelectMimeType.ofVideo()
|
|
102
|
+
MediaType.ALL -> SelectMimeType.ofAll()
|
|
103
|
+
else -> SelectMimeType.ofImage()
|
|
104
|
+
}
|
|
105
|
+
}
|