rns-mediapicker 0.0.4 → 0.0.6

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 CHANGED
@@ -52,6 +52,8 @@ pick(useCamera, mediaType, env)
52
52
 
53
53
  ```bash
54
54
  response-object
55
+ ```
56
+ ```js
55
57
  {
56
58
  uri: string; // path to the processed file
57
59
  width: number; // media width
@@ -59,8 +61,11 @@ response-object
59
61
  type: string; // 'image' or 'video'
60
62
  isVideo: boolean; // helper flag
61
63
  }
62
-
63
- -features-included
64
+ ```
65
+ ```bash
66
+ features-included
67
+ ```
68
+ ```js
64
69
  1. Android 11+ Intent Queries: Automatically handled via plugin.
65
70
  2. FileProvider: Safe URI sharing handled internally.
66
71
  3. iOS Permissions: Usage descriptions injected into Info.plist.
@@ -1,5 +1,6 @@
1
1
  package com.rnsmediapicker
2
2
 
3
+ import android.Manifest
3
4
  import android.app.Activity
4
5
  import android.content.Intent
5
6
  import android.graphics.*
@@ -27,10 +28,12 @@ class FastMediaPickerModule(
27
28
  private var pickerPromise: Promise? = null
28
29
  private var cameraCaptureUri: Uri? = null
29
30
  private var pendingFrontCamera = false
31
+ private var pendingUseCamera = false
32
+ private var pendingMediaType: String? = null
30
33
 
31
34
  companion object {
32
35
  private const val PICKER_REQUEST_CODE = 4123
33
- private const val CAMERA_PERMISSION_REQUEST = 9123
36
+ private const val PERMISSION_REQUEST = 9123
34
37
  private const val MAX_IMAGE_SIZE = 1200
35
38
  private const val JPEG_QUALITY = 85
36
39
  }
@@ -41,31 +44,13 @@ class FastMediaPickerModule(
41
44
 
42
45
  override fun getName(): String = "FastMediaPicker"
43
46
 
44
- // -------------------- REQUIRED EVENT CONTRACT --------------------
47
+ @ReactMethod fun addListener(eventName: String) {}
45
48
 
46
- @ReactMethod
47
- fun addListener(eventName: String) {
48
- // Required for RN event emitter (even if unused)
49
- }
50
-
51
- @ReactMethod
52
- fun removeListeners(count: Int) {
53
- // Required for RN event emitter (even if unused)
54
- }
55
-
56
- private fun sendLog(message: String) {
57
- android.util.Log.d("FastMediaPicker", message)
58
- try {
59
- reactContext
60
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
61
- .emit("onPickerLog", message)
62
- } catch (_: Exception) {
63
- }
64
- }
49
+ @ReactMethod fun removeListeners(count: Int) {}
65
50
 
66
51
  // -------------------- PUBLIC API --------------------
67
52
 
68
- @ReactMethod
53
+ @ReactMethod
69
54
  fun pick(
70
55
  useCamera: Boolean,
71
56
  mediaType: String,
@@ -74,43 +59,126 @@ class FastMediaPickerModule(
74
59
  ) {
75
60
  val activity =
76
61
  currentActivity ?: run {
77
- promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist")
62
+ promise.reject("E_NO_ACTIVITY", "Activity not found")
78
63
  return
79
64
  }
80
65
 
81
66
  cleanup()
82
67
  pickerPromise = promise
68
+ pendingUseCamera = useCamera
69
+ pendingMediaType = mediaType
83
70
  pendingFrontCamera = env?.lowercase() == "front"
84
71
 
85
- try {
86
- if (useCamera) {
87
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
88
- activity.checkSelfPermission(android.Manifest.permission.CAMERA)
89
- != android.content.pm.PackageManager.PERMISSION_GRANTED
90
- ) {
91
- val permissionAwareActivity =
92
- activity as? PermissionAwareActivity
93
- ?: run {
94
- promise.reject("E_ACTIVITY_NOT_PERMISSION_AWARE", "Activity not PermissionAware")
95
- cleanup()
96
- return
97
- }
98
-
99
- permissionAwareActivity.requestPermissions(
100
- arrayOf(android.Manifest.permission.CAMERA),
101
- CAMERA_PERMISSION_REQUEST,
102
- this,
103
- )
104
- return
105
- }
106
- launchCamera(activity, pendingFrontCamera)
72
+ val permissions = mutableListOf<String>()
73
+
74
+ if (useCamera) {
75
+ // Camera always needs permission
76
+ permissions.add(Manifest.permission.CAMERA)
77
+ } else {
78
+ // GALLERY LOGIC:
79
+ // Android 13 (API 33) and above use the Photo Picker which REQUIRES NO PERMISSIONS.
80
+ // We only request storage permissions for older devices (API 32 and below).
81
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
82
+ permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
83
+ }
84
+ }
85
+
86
+ // Filter out permissions that are already granted
87
+ val missing = permissions.filter {
88
+ activity.checkSelfPermission(it) != android.content.pm.PackageManager.PERMISSION_GRANTED
89
+ }
90
+
91
+ if (missing.isNotEmpty()) {
92
+ val pa = activity as? PermissionAwareActivity ?: run {
93
+ promise.reject("E_NOT_PERMISSION_AWARE", "Activity not PermissionAware")
94
+ cleanup()
95
+ return
96
+ }
97
+
98
+ pa.requestPermissions(
99
+ missing.toTypedArray(),
100
+ PERMISSION_REQUEST,
101
+ this,
102
+ )
103
+ return
104
+ }
105
+
106
+ // All permissions granted (or none needed), launch the picker/camera
107
+ launch(activity)
108
+ }
109
+
110
+ // Android 14 (API 34+) introduces Partial Access
111
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
112
+ permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
113
+ permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
114
+ permissions.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
115
+ }
116
+ // Android 13 (API 33)
117
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
118
+ if (mediaType == "video") {
119
+ permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
120
+ } else if (mediaType == "image") {
121
+ permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
107
122
  } else {
108
- launchPicker(activity, mediaType.lowercase())
123
+ permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
124
+ permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
109
125
  }
110
- } catch (e: Exception) {
111
- promise.reject("E_LAUNCH_FAILED", e.message)
126
+ }
127
+ // Android 12 and below
128
+ else {
129
+ permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
130
+ }
131
+
132
+ val missing =
133
+ permissions.filter {
134
+ activity.checkSelfPermission(it) !=
135
+ android.content.pm.PackageManager.PERMISSION_GRANTED
136
+ }
137
+
138
+ if (missing.isNotEmpty()) {
139
+ val pa =
140
+ activity as? PermissionAwareActivity ?: run {
141
+ promise.reject("E_NOT_PERMISSION_AWARE", "Activity not PermissionAware")
142
+ cleanup()
143
+ return
144
+ }
145
+
146
+ pa.requestPermissions(
147
+ missing.toTypedArray(),
148
+ PERMISSION_REQUEST,
149
+ this,
150
+ )
151
+ return
152
+ }
153
+
154
+ launch(activity)
155
+ }
156
+
157
+ private fun launch(activity: Activity) {
158
+ if (pendingUseCamera) {
159
+ launchCamera(activity, pendingFrontCamera)
160
+ } else {
161
+ launchPicker(activity, pendingMediaType ?: "image")
162
+ }
163
+ }
164
+
165
+ // -------------------- PERMISSIONS CALLBACK --------------------
166
+
167
+ override fun onRequestPermissionsResult(
168
+ requestCode: Int,
169
+ permissions: Array<String>,
170
+ grantResults: IntArray,
171
+ ): Boolean {
172
+ if (requestCode != PERMISSION_REQUEST) return false
173
+
174
+ if (grantResults.any { it != android.content.pm.PackageManager.PERMISSION_GRANTED }) {
175
+ pickerPromise?.reject("E_PERMISSION_DENIED", "Required permission denied")
112
176
  cleanup()
177
+ return true
113
178
  }
179
+
180
+ currentActivity?.let { launch(it) }
181
+ return true
114
182
  }
115
183
 
116
184
  // -------------------- PICKER / CAMERA --------------------
@@ -119,51 +187,60 @@ class FastMediaPickerModule(
119
187
  activity: Activity,
120
188
  type: String,
121
189
  ) {
190
+ // 1. Try the Modern Photo Picker (Android 13+ or backported to 11+)
191
+ // We check for the action string directly to handle backported devices
122
192
  val intent =
123
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
124
- Intent(MediaStore.ACTION_PICK_IMAGES).apply {
125
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
126
- this.type =
127
- when (type) {
128
- "image" -> "image/*"
129
- "video" -> "video/*"
130
- else -> "*/*"
131
- }
193
+ Intent("android.provider.action.PICK_IMAGES").apply {
194
+ when (type) {
195
+ "video" -> this.type = "video/*"
196
+ "image" -> this.type = "image/*"
197
+ // For 'both', we leave the type null or specify multiple if needed
132
198
  }
133
- } else {
134
- Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
199
+ }
200
+
201
+ try {
202
+ activity.startActivityForResult(intent, PICKER_REQUEST_CODE)
203
+ } catch (e: Exception) {
204
+ // 2. Fallback: Legacy ACTION_GET_CONTENT (Android 10 and below, or failed 13+)
205
+ val fallbackIntent =
206
+ Intent(Intent.ACTION_GET_CONTENT).apply {
135
207
  addCategory(Intent.CATEGORY_OPENABLE)
136
- addFlags(
137
- Intent.FLAG_GRANT_READ_URI_PERMISSION or
138
- Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
139
- )
140
208
  this.type =
141
209
  when (type) {
142
- "image" -> "image/*"
143
210
  "video" -> "video/*"
144
- else -> "*/*"
211
+ "image" -> "image/*"
212
+ else -> "*/*" // 'both' case
145
213
  }
214
+ // Allow both images and videos in the legacy picker if 'both' is selected
215
+ if (type == "both") {
216
+ putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
217
+ }
146
218
  }
147
- }
148
219
 
149
- activity.startActivityForResult(intent, PICKER_REQUEST_CODE)
220
+ try {
221
+ activity.startActivityForResult(
222
+ Intent.createChooser(fallbackIntent, "Select Media"),
223
+ PICKER_REQUEST_CODE,
224
+ )
225
+ } catch (fallbackEx: Exception) {
226
+ pickerPromise?.reject("E_NO_PICKER", "No app available to pick media")
227
+ cleanup()
228
+ }
229
+ }
150
230
  }
151
231
 
152
232
  private fun launchCamera(
153
233
  activity: Activity,
154
234
  useFrontCamera: Boolean,
155
235
  ) {
156
- val photoFile = createTempFile("jpg")
236
+ val file = createTempFile("jpg")
157
237
  val authority = "${reactContext.packageName}.provider"
158
- cameraCaptureUri = FileProvider.getUriForFile(reactContext, authority, photoFile)
238
+ cameraCaptureUri = FileProvider.getUriForFile(reactContext, authority, file)
159
239
 
160
240
  val intent =
161
241
  Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
162
242
  putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureUri)
163
- addFlags(
164
- Intent.FLAG_GRANT_READ_URI_PERMISSION or
165
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
166
- )
243
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
167
244
  if (useFrontCamera) {
168
245
  putExtra("android.intent.extras.CAMERA_FACING", 1)
169
246
  putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
@@ -173,7 +250,7 @@ class FastMediaPickerModule(
173
250
  activity.startActivityForResult(intent, PICKER_REQUEST_CODE)
174
251
  }
175
252
 
176
- // -------------------- ACTIVITY CALLBACKS --------------------
253
+ // -------------------- ACTIVITY RESULT --------------------
177
254
 
178
255
  override fun onActivityResult(
179
256
  activity: Activity?,
@@ -183,184 +260,67 @@ class FastMediaPickerModule(
183
260
  ) {
184
261
  if (requestCode != PICKER_REQUEST_CODE || pickerPromise == null) return
185
262
 
186
- if (resultCode == Activity.RESULT_CANCELED) {
263
+ if (resultCode != Activity.RESULT_OK) {
187
264
  pickerPromise?.reject("E_CANCELLED", "User cancelled")
188
265
  cleanup()
189
266
  return
190
267
  }
191
268
 
192
269
  val uri = data?.data ?: cameraCaptureUri
193
-
194
- if (uri != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
195
- try {
196
- reactContext.contentResolver.takePersistableUriPermission(
197
- uri,
198
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
199
- )
200
- } catch (_: Exception) {
201
- }
202
- }
203
-
204
270
  if (uri != null) {
205
271
  processMedia(uri)
206
272
  } else {
207
- pickerPromise?.reject("E_NO_URI", "No media selected")
208
- cleanup()
273
+ pickerPromise?.reject("E_NO_URI", "No media")
209
274
  }
210
275
  }
211
276
 
212
- override fun onRequestPermissionsResult(
213
- requestCode: Int,
214
- permissions: Array<String>,
215
- grantResults: IntArray,
216
- ): Boolean {
217
- if (requestCode == CAMERA_PERMISSION_REQUEST) {
218
- if (grantResults.isNotEmpty() &&
219
- grantResults[0] == android.content.pm.PackageManager.PERMISSION_GRANTED
220
- ) {
221
- currentActivity?.let {
222
- launchCamera(it, pendingFrontCamera)
223
- }
224
- } else {
225
- pickerPromise?.reject("E_CAMERA_PERMISSION", "Camera permission denied")
226
- cleanup()
227
- }
228
- return true
229
- }
230
- return false
231
- }
232
-
233
- // -------------------- MEDIA PROCESSING --------------------
277
+ // -------------------- MEDIA --------------------
234
278
 
235
279
  private fun processMedia(uri: Uri) {
236
280
  val resolver = reactContext.contentResolver
237
- val mimeType = resolver.getType(uri) ?: "image/jpeg"
238
- val isVideo = mimeType.startsWith("video/")
281
+ val mime = resolver.getType(uri) ?: "image/jpeg"
282
+ val isVideo = mime.startsWith("video")
239
283
 
240
284
  try {
241
285
  if (isVideo) {
242
- val ext =
243
- MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "mp4"
244
- val tempFile = createTempFile(ext)
245
-
286
+ val file = createTempFile("mp4")
246
287
  resolver.openInputStream(uri)?.use { input ->
247
- FileOutputStream(tempFile).use { output -> input.copyTo(output) }
288
+ FileOutputStream(file).use { input.copyTo(it) }
248
289
  }
249
-
250
- val (w, h) = getVideoDimensions(tempFile)
251
- resolveResult(Uri.fromFile(tempFile).toString(), true, w, h)
290
+ resolveResult(Uri.fromFile(file).toString(), true, 0, 0)
252
291
  } else {
253
- val tempFile = processAndSaveImage(uri)
254
- val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
255
- BitmapFactory.decodeFile(tempFile.absolutePath, opts)
256
- resolveResult(
257
- Uri.fromFile(tempFile).toString(),
258
- false,
259
- opts.outWidth,
260
- opts.outHeight,
261
- )
292
+ val file = createTempFile("jpg")
293
+ resolver.openInputStream(uri)?.use { input ->
294
+ BitmapFactory.decodeStream(input)?.compress(
295
+ Bitmap.CompressFormat.JPEG,
296
+ JPEG_QUALITY,
297
+ FileOutputStream(file),
298
+ )
299
+ }
300
+ resolveResult(Uri.fromFile(file).toString(), false, 0, 0)
262
301
  }
263
302
  } catch (e: Exception) {
264
- pickerPromise?.reject("E_PROCESS_ERROR", e.message)
303
+ pickerPromise?.reject("E_PROCESS", e.message)
265
304
  } finally {
266
305
  cleanup()
267
306
  }
268
307
  }
269
308
 
270
- private fun getVideoDimensions(file: File): Pair<Int, Int> {
271
- val retriever = MediaMetadataRetriever()
272
- return try {
273
- retriever.setDataSource(file.absolutePath)
274
- val w =
275
- retriever
276
- .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
277
- ?.toInt() ?: 0
278
- val h =
279
- retriever
280
- .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
281
- ?.toInt() ?: 0
282
- val rot =
283
- retriever
284
- .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
285
- ?.toInt() ?: 0
286
- if (rot == 90 || rot == 270) h to w else w to h
287
- } finally {
288
- retriever.release()
289
- }
290
- }
291
-
292
- private fun processAndSaveImage(uri: Uri): File {
293
- val tempFile = createTempFile("jpg")
294
- val resolver = reactContext.contentResolver
295
-
296
- val opts =
297
- BitmapFactory.Options().apply {
298
- inJustDecodeBounds = true
299
- resolver.openInputStream(uri)?.use {
300
- BitmapFactory.decodeStream(it, null, this)
301
- }
302
- inSampleSize =
303
- calculateInSampleSize(outWidth, outHeight, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
304
- inJustDecodeBounds = false
305
- }
306
-
307
- val original =
308
- resolver.openInputStream(uri)?.use {
309
- BitmapFactory.decodeStream(it, null, opts)
310
- } ?: throw RuntimeException("Decode failed")
311
-
312
- val result =
313
- Bitmap.createBitmap(original.width, original.height, Bitmap.Config.ARGB_8888)
314
-
315
- Canvas(result).apply {
316
- drawColor(Color.WHITE)
317
- drawBitmap(original, 0f, 0f, null)
318
- }
319
-
320
- FileOutputStream(tempFile).use {
321
- result.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, it)
322
- }
323
-
324
- original.recycle()
325
- result.recycle()
326
- return tempFile
327
- }
328
-
329
- private fun calculateInSampleSize(
330
- w: Int,
331
- h: Int,
332
- reqW: Int,
333
- reqH: Int,
334
- ): Int {
335
- var size = 1
336
- if (h > reqH || w > reqW) {
337
- val halfH = h / 2
338
- val halfW = w / 2
339
- while (halfH / size >= reqH && halfW / size >= reqW) {
340
- size *= 2
341
- }
342
- }
343
- return size
344
- }
345
-
346
309
  private fun resolveResult(
347
310
  uri: String,
348
311
  isVideo: Boolean,
349
312
  w: Int,
350
313
  h: Int,
351
314
  ) {
352
- val map =
315
+ pickerPromise?.resolve(
353
316
  Arguments.createMap().apply {
354
317
  putString("uri", uri)
355
318
  putBoolean("isVideo", isVideo)
356
- putString("type", if (isVideo) "video" else "image")
357
- putDouble("width", w.toDouble())
358
- putDouble("height", h.toDouble())
359
- }
360
- pickerPromise?.resolve(map)
319
+ },
320
+ )
361
321
  }
362
322
 
363
- private fun createTempFile(ext: String): File = File(reactContext.cacheDir, "fast-${UUID.randomUUID()}.$ext")
323
+ private fun createTempFile(ext: String) = File(reactContext.cacheDir, "fast-${UUID.randomUUID()}.$ext")
364
324
 
365
325
  private fun cleanup() {
366
326
  pickerPromise = null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-mediapicker",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "High-performance React Native module for picking media on Android and iOS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -3,74 +3,98 @@ const { withAndroidManifest, withInfoPlist, AndroidConfig } = require('@expo/con
3
3
  const { getMainApplicationOrThrow } = AndroidConfig.Manifest;
4
4
 
5
5
  /**
6
- * Android Configuration
6
+ * ANDROID CONFIG
7
7
  */
8
8
  const withAndroidConfig = (config) => {
9
9
  return withAndroidManifest(config, (config) => {
10
10
  const androidManifest = config.modResults;
11
11
 
12
- // 1. Add Permissions
13
- const permissions = [
14
- 'android.permission.CAMERA',
15
- 'android.permission.READ_EXTERNAL_STORAGE',
16
- 'android.permission.WRITE_EXTERNAL_STORAGE',
17
- 'android.permission.READ_MEDIA_IMAGES',
18
- 'android.permission.READ_MEDIA_VIDEO',
19
- ];
12
+ // -------------------------
13
+ // 1. PERMISSIONS
14
+ // -------------------------
20
15
 
21
16
  if (!androidManifest.manifest['uses-permission']) {
22
17
  androidManifest.manifest['uses-permission'] = [];
23
18
  }
24
19
 
20
+ const permissions = [
21
+ { name: 'android.permission.CAMERA' },
22
+ { name: 'android.permission.READ_MEDIA_IMAGES' },
23
+ { name: 'android.permission.READ_MEDIA_VIDEO' },
24
+ { name: 'android.permission.READ_EXTERNAL_STORAGE', maxSdkVersion: '32' },
25
+ ];
26
+
25
27
  permissions.forEach((perm) => {
26
- if (!androidManifest.manifest['uses-permission'].find((p) => p.$['android:name'] === perm)) {
27
- androidManifest.manifest['uses-permission'].push({ $: { 'android:name': perm } });
28
+ const exists = androidManifest.manifest['uses-permission'].some(
29
+ (p) => p.$['android:name'] === perm.name
30
+ );
31
+
32
+ if (!exists) {
33
+ androidManifest.manifest['uses-permission'].push({
34
+ $: {
35
+ 'android:name': perm.name,
36
+ ...(perm.maxSdkVersion
37
+ ? { 'android:maxSdkVersion': perm.maxSdkVersion }
38
+ : {}),
39
+ },
40
+ });
28
41
  }
29
42
  });
30
43
 
31
- // 2. Add Queries (For Android 11+ visibility)
44
+ // -------------------------
45
+ // 2. QUERIES (Android 11+)
46
+ // -------------------------
47
+
32
48
  if (!androidManifest.manifest.queries) {
33
49
  androidManifest.manifest.queries = [{}];
34
50
  }
51
+
35
52
  const queries = androidManifest.manifest.queries[0];
36
53
  if (!queries.intent) queries.intent = [];
37
54
 
38
- const pickerIntents = [
55
+ const intents = [
39
56
  { action: [{ $: { 'android:name': 'android.media.action.IMAGE_CAPTURE' } }] },
40
57
  { action: [{ $: { 'android:name': 'android.provider.action.PICK_IMAGES' } }] },
41
58
  {
42
59
  action: [{ $: { 'android:name': 'android.intent.action.OPEN_DOCUMENT' } }],
43
- data: [{ $: { 'android:mimeType': 'image/*' } }]
60
+ data: [{ $: { 'android:mimeType': 'image/*' } }],
44
61
  },
45
62
  {
46
63
  action: [{ $: { 'android:name': 'android.intent.action.OPEN_DOCUMENT' } }],
47
- data: [{ $: { 'android:mimeType': 'video/*' } }]
48
- }
64
+ data: [{ $: { 'android:mimeType': 'video/*' } }],
65
+ },
49
66
  ];
50
67
 
51
- pickerIntents.forEach((intent) => {
52
- const intentName = intent.action[0].$['android:name'];
53
- const exists = queries.intent.find(i => i.action[0].$['android:name'] === intentName);
68
+ intents.forEach((intent) => {
69
+ const actionName = intent.action[0].$['android:name'];
70
+ const exists = queries.intent.some(
71
+ (i) => i.action?.[0]?.$['android:name'] === actionName
72
+ );
54
73
  if (!exists) queries.intent.push(intent);
55
74
  });
56
75
 
57
- // 3. Ensure FileProvider exists (The "Null" Fix)
76
+ // -------------------------
77
+ // 3. FILE PROVIDER
78
+ // -------------------------
79
+
58
80
  const mainApplication = getMainApplicationOrThrow(androidManifest);
81
+
59
82
  if (!mainApplication.provider) {
60
83
  mainApplication.provider = [];
61
84
  }
62
85
 
63
- const providerClassName = 'androidx.core.content.FileProvider';
64
86
  const providerAuthority = '${applicationId}.provider';
65
87
 
66
- const hasProvider = mainApplication.provider.find(
67
- (p) => p.$['android:name'] === providerClassName || p.$['android:authorities'] === providerAuthority
88
+ const hasProvider = mainApplication.provider.some(
89
+ (p) =>
90
+ p.$['android:name'] === 'androidx.core.content.FileProvider' ||
91
+ p.$['android:authorities'] === providerAuthority
68
92
  );
69
93
 
70
94
  if (!hasProvider) {
71
95
  mainApplication.provider.push({
72
96
  $: {
73
- 'android:name': providerClassName,
97
+ 'android:name': 'androidx.core.content.FileProvider',
74
98
  'android:authorities': providerAuthority,
75
99
  'android:exported': 'false',
76
100
  'android:grantUriPermissions': 'true',
@@ -91,26 +115,33 @@ const withAndroidConfig = (config) => {
91
115
  };
92
116
 
93
117
  /**
94
- * iOS Configuration
118
+ * IOS CONFIG
95
119
  */
96
120
  const withIosConfig = (config) => {
97
121
  return withInfoPlist(config, (config) => {
98
- config.modResults.NSCameraUsageDescription =
99
- config.modResults.NSCameraUsageDescription || "Allow $(PRODUCT_NAME) to access your camera to take photos and videos.";
100
- config.modResults.NSPhotoLibraryUsageDescription =
101
- config.modResults.NSPhotoLibraryUsageDescription || "Allow $(PRODUCT_NAME) to access your photo library to select media.";
102
- config.modResults.NSMicrophoneUsageDescription =
103
- config.modResults.NSMicrophoneUsageDescription || "Allow $(PRODUCT_NAME) to access your microphone when recording video.";
122
+ const plist = config.modResults;
123
+
124
+ plist.NSCameraUsageDescription =
125
+ plist.NSCameraUsageDescription ||
126
+ 'Allow access to your camera to take photos and videos.';
127
+
128
+ plist.NSPhotoLibraryUsageDescription =
129
+ plist.NSPhotoLibraryUsageDescription ||
130
+ 'Allow access to your photo library to select media.';
131
+
132
+ plist.NSMicrophoneUsageDescription =
133
+ plist.NSMicrophoneUsageDescription ||
134
+ 'Allow access to your microphone when recording videos.';
104
135
 
105
136
  return config;
106
137
  });
107
138
  };
108
139
 
109
140
  /**
110
- * Export the Plugin
141
+ * EXPORT PLUGIN
111
142
  */
112
143
  module.exports = function withMediaPicker(config) {
113
144
  config = withAndroidConfig(config);
114
145
  config = withIosConfig(config);
115
146
  return config;
116
- };
147
+ };