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.
Files changed (99) hide show
  1. package/README.md +627 -0
  2. package/android/CMakeLists.txt +30 -0
  3. package/android/build.gradle +79 -0
  4. package/android/proguard-rules.pro +21 -0
  5. package/android/src/main/AndroidManifest.xml +39 -0
  6. package/android/src/main/kotlin/com/margelo/pictureselector/GlideEngine.kt +80 -0
  7. package/android/src/main/kotlin/com/margelo/pictureselector/HybridPictureSelector.kt +138 -0
  8. package/android/src/main/kotlin/com/margelo/pictureselector/LubanCompressEngine.kt +58 -0
  9. package/android/src/main/kotlin/com/margelo/pictureselector/MediaAssetMapper.kt +69 -0
  10. package/android/src/main/kotlin/com/margelo/pictureselector/NitroPictureSelectorPackage.kt +52 -0
  11. package/android/src/main/kotlin/com/margelo/pictureselector/PictureSelectorOptionsMapper.kt +105 -0
  12. package/android/src/main/kotlin/com/margelo/pictureselector/UCropEngine.kt +57 -0
  13. package/android/src/main/res/xml/file_paths.xml +8 -0
  14. package/ios/HybridPictureSelector.swift +386 -0
  15. package/ios/NitroPictureSelector.podspec +39 -0
  16. package/lib/commonjs/PictureSelector.js +74 -0
  17. package/lib/commonjs/PictureSelector.js.map +1 -0
  18. package/lib/commonjs/index.js +39 -0
  19. package/lib/commonjs/index.js.map +1 -0
  20. package/lib/commonjs/package.json +1 -0
  21. package/lib/commonjs/specs/PictureSelector.nitro.js +34 -0
  22. package/lib/commonjs/specs/PictureSelector.nitro.js.map +1 -0
  23. package/lib/commonjs/types.js +44 -0
  24. package/lib/commonjs/types.js.map +1 -0
  25. package/lib/commonjs/usePictureSelector.js +122 -0
  26. package/lib/commonjs/usePictureSelector.js.map +1 -0
  27. package/lib/module/PictureSelector.js +71 -0
  28. package/lib/module/PictureSelector.js.map +1 -0
  29. package/lib/module/index.js +6 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/package.json +1 -0
  32. package/lib/module/specs/PictureSelector.nitro.js +36 -0
  33. package/lib/module/specs/PictureSelector.nitro.js.map +1 -0
  34. package/lib/module/types.js +29 -0
  35. package/lib/module/types.js.map +1 -0
  36. package/lib/module/usePictureSelector.js +119 -0
  37. package/lib/module/usePictureSelector.js.map +1 -0
  38. package/lib/typescript/PictureSelector.d.ts +23 -0
  39. package/lib/typescript/PictureSelector.d.ts.map +1 -0
  40. package/lib/typescript/index.d.ts +6 -0
  41. package/lib/typescript/index.d.ts.map +1 -0
  42. package/lib/typescript/specs/PictureSelector.nitro.d.ts +96 -0
  43. package/lib/typescript/specs/PictureSelector.nitro.d.ts.map +1 -0
  44. package/lib/typescript/types.d.ts +16 -0
  45. package/lib/typescript/types.d.ts.map +1 -0
  46. package/lib/typescript/usePictureSelector.d.ts +26 -0
  47. package/lib/typescript/usePictureSelector.d.ts.map +1 -0
  48. package/nitro.json +11 -0
  49. package/nitrogen/generated/.gitattributes +1 -0
  50. package/nitrogen/generated/android/NitroPictureSelector+autolinking.cmake +81 -0
  51. package/nitrogen/generated/android/NitroPictureSelector+autolinking.gradle +27 -0
  52. package/nitrogen/generated/android/NitroPictureSelectorOnLoad.cpp +41 -0
  53. package/nitrogen/generated/android/NitroPictureSelectorOnLoad.hpp +34 -0
  54. package/nitrogen/generated/android/c++/JCompressOptions.hpp +69 -0
  55. package/nitrogen/generated/android/c++/JCropOptions.hpp +73 -0
  56. package/nitrogen/generated/android/c++/JHybridHybridPictureSelectorSpec.cpp +125 -0
  57. package/nitrogen/generated/android/c++/JHybridHybridPictureSelectorSpec.hpp +64 -0
  58. package/nitrogen/generated/android/c++/JMediaAsset.hpp +98 -0
  59. package/nitrogen/generated/android/c++/JMediaType.hpp +61 -0
  60. package/nitrogen/generated/android/c++/JPickerTheme.hpp +64 -0
  61. package/nitrogen/generated/android/c++/JPictureSelectorOptions.hpp +121 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/CompressOptions.kt +47 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/CropOptions.kt +50 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/HybridHybridPictureSelectorSpec.kt +59 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/MediaAsset.kt +68 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/MediaType.kt +24 -0
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/NitroPictureSelectorOnLoad.kt +35 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/PickerTheme.kt +25 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/margelo/pictureselector/PictureSelectorOptions.kt +65 -0
  70. package/nitrogen/generated/ios/NitroPictureSelector+autolinking.rb +60 -0
  71. package/nitrogen/generated/ios/NitroPictureSelector-Swift-Cxx-Bridge.cpp +49 -0
  72. package/nitrogen/generated/ios/NitroPictureSelector-Swift-Cxx-Bridge.hpp +270 -0
  73. package/nitrogen/generated/ios/NitroPictureSelector-Swift-Cxx-Umbrella.hpp +65 -0
  74. package/nitrogen/generated/ios/c++/HybridHybridPictureSelectorSpecSwift.cpp +11 -0
  75. package/nitrogen/generated/ios/c++/HybridHybridPictureSelectorSpecSwift.hpp +110 -0
  76. package/nitrogen/generated/ios/swift/CompressOptions.swift +83 -0
  77. package/nitrogen/generated/ios/swift/CropOptions.swift +101 -0
  78. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  79. package/nitrogen/generated/ios/swift/Func_void_std__vector_MediaAsset_.swift +46 -0
  80. package/nitrogen/generated/ios/swift/HybridHybridPictureSelectorSpec.swift +56 -0
  81. package/nitrogen/generated/ios/swift/HybridHybridPictureSelectorSpec_cxx.swift +176 -0
  82. package/nitrogen/generated/ios/swift/MediaAsset.swift +118 -0
  83. package/nitrogen/generated/ios/swift/MediaType.swift +44 -0
  84. package/nitrogen/generated/ios/swift/PickerTheme.swift +48 -0
  85. package/nitrogen/generated/ios/swift/PictureSelectorOptions.swift +182 -0
  86. package/nitrogen/generated/shared/c++/CompressOptions.hpp +95 -0
  87. package/nitrogen/generated/shared/c++/CropOptions.hpp +99 -0
  88. package/nitrogen/generated/shared/c++/HybridHybridPictureSelectorSpec.cpp +22 -0
  89. package/nitrogen/generated/shared/c++/HybridHybridPictureSelectorSpec.hpp +69 -0
  90. package/nitrogen/generated/shared/c++/MediaAsset.hpp +124 -0
  91. package/nitrogen/generated/shared/c++/MediaType.hpp +80 -0
  92. package/nitrogen/generated/shared/c++/PickerTheme.hpp +84 -0
  93. package/nitrogen/generated/shared/c++/PictureSelectorOptions.hpp +132 -0
  94. package/package.json +76 -0
  95. package/src/PictureSelector.ts +72 -0
  96. package/src/index.ts +16 -0
  97. package/src/specs/PictureSelector.nitro.ts +121 -0
  98. package/src/types.ts +38 -0
  99. 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
+ }