expo-image-picker 10.2.3 → 11.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 (34) hide show
  1. package/CHANGELOG.md +17 -6
  2. package/android/build.gradle +6 -13
  3. package/android/src/main/java/expo/modules/imagepicker/ImagePickerConstants.kt +7 -2
  4. package/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt +66 -24
  5. package/android/src/main/java/expo/modules/imagepicker/ImagePickerOptions.kt +1 -1
  6. package/android/src/main/java/expo/modules/imagepicker/ImagePickerPackage.kt +1 -1
  7. package/android/src/main/java/expo/modules/imagepicker/ImagePickerUtils.kt +29 -1
  8. package/android/src/main/java/expo/modules/imagepicker/PendingPromise.kt +9 -7
  9. package/android/src/main/java/expo/modules/imagepicker/fileproviders/CacheFileProvider.kt +1 -1
  10. package/android/src/main/java/expo/modules/imagepicker/tasks/ImageResultTask.kt +73 -31
  11. package/android/src/main/java/expo/modules/imagepicker/tasks/VideoResultTask.kt +52 -25
  12. package/build/ExponentImagePicker.d.ts +1 -1
  13. package/build/ExponentImagePicker.js +1 -1
  14. package/build/ExponentImagePicker.js.map +1 -1
  15. package/build/ExponentImagePicker.web.js +2 -3
  16. package/build/ExponentImagePicker.web.js.map +1 -1
  17. package/build/ImagePicker.d.ts +24 -2
  18. package/build/ImagePicker.js +30 -2
  19. package/build/ImagePicker.js.map +1 -1
  20. package/build/ImagePicker.types.d.ts +12 -0
  21. package/build/ImagePicker.types.js +12 -0
  22. package/build/ImagePicker.types.js.map +1 -1
  23. package/ios/EXImagePicker/EXImagePicker.h +3 -3
  24. package/ios/EXImagePicker/EXImagePicker.m +52 -56
  25. package/ios/EXImagePicker/Permissions/EXImagePickerCameraPermissionRequester.m +5 -5
  26. package/ios/EXImagePicker/Permissions/EXImagePickerMediaLibraryPermissionRequester.m +3 -3
  27. package/ios/EXImagePicker.podspec +2 -2
  28. package/package.json +5 -8
  29. package/plugin/build/withImagePicker.js +1 -1
  30. package/plugin/src/withImagePicker.ts +2 -2
  31. package/src/ExponentImagePicker.ts +1 -1
  32. package/src/ExponentImagePicker.web.ts +2 -3
  33. package/src/ImagePicker.ts +43 -2
  34. package/src/ImagePicker.types.ts +19 -7
package/CHANGELOG.md CHANGED
@@ -10,17 +10,26 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 10.2.3 — 2021-08-20
13
+ ## 11.0.0 — 2021-09-28
14
14
 
15
- _This version does not introduce any user-facing changes._
15
+ ### 🛠 Breaking changes
16
16
 
17
- ## 10.2.2 2021-06-24
17
+ - Dropped support for iOS 11.0 ([#14383](https://github.com/expo/expo/pull/14383) by [@cruzach](https://github.com/cruzach))
18
18
 
19
- _This version does not introduce any user-facing changes._
19
+ ### 🎉 New features
20
20
 
21
- ## 10.2.1 2021-06-22
21
+ - Add useCameraPermissions and useMediaLibraryPermissions hooks from modules factory. ([#13859](https://github.com/expo/expo/pull/13859) by [@bycedric](https://github.com/bycedric))
22
+ - Add possibility to change presentation style on iOS. ([#14068](https://github.com/expo/expo/pull/14068) by [@mstach60161](https://github.com/mstach60161))
22
23
 
23
- _This version does not introduce any user-facing changes._
24
+ ### 🐛 Bug fixes
25
+
26
+ - Add missing `GPSHPositioningError` exif parameter on Android. ([#13998](https://github.com/expo/expo/pull/13998) by [@mstach60161](https://github.com/mstach60161))
27
+ - Fix promise not resolving when the app is moved to the background on Android. ([#13975](https://github.com/expo/expo/pull/13975) by [@mstach60161](https://github.com/mstach60161))
28
+ - Fix building errors from use_frameworks! in Podfile. ([#14523](https://github.com/expo/expo/pull/14523) by [@kudo](https://github.com/kudo))
29
+
30
+ ### 💡 Others
31
+
32
+ - Updated `@expo/config-plugins` ([#14443](https://github.com/expo/expo/pull/14443) by [@EvanBacon](https://github.com/EvanBacon))
24
33
 
25
34
  ## 10.2.0 — 2021-06-16
26
35
 
@@ -29,10 +38,12 @@ _This version does not introduce any user-facing changes._
29
38
  - Fixed `base64` return on web. ([#12529](https://github.com/expo/expo/pull/12529) by [@simonezuccala](https://github.com/simonezuccala) and [@misterdev](https://github.com/misterdev))
30
39
  - Enable kotlin in all modules. ([#12716](https://github.com/expo/expo/pull/12716) by [@wschurman](https://github.com/wschurman))
31
40
  - Fixed cases where Picker & Camera would be transparent on iOS 14.5. ([#12897](https://github.com/expo/expo/pull/12897) by [@cruzach](https://github.com/cruzach))
41
+ - Add missing exif data to cropped image on Android. ([#14038](https://github.com/expo/expo/pull/14038) by [@mstach60161](https://github.com/mstach60161))
32
42
 
33
43
  ### 💡 Others
34
44
 
35
45
  - Migrated from `unimodules-file-system-interface` and `unimodules-permissions-interface` to `expo-modules-core`. ([#12961](https://github.com/expo/expo/pull/12961) by [@tsapeta](https://github.com/tsapeta))
46
+ - Migrated from `AsyncTask` to Kotlin concurrency utilities. ([#13800](https://github.com/expo/expo/pull/13800) by [@m1st4ke](https://github.com/m1st4ke))
36
47
 
37
48
  ## 10.1.3 — 2021-04-13
38
49
 
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '10.2.3'
6
+ version = '11.0.0'
7
7
 
8
8
  buildscript {
9
9
  // Simple helper that allows the root project to override versions declared by this library.
@@ -58,29 +58,22 @@ android {
58
58
  minSdkVersion safeExtGet("minSdkVersion", 21)
59
59
  targetSdkVersion safeExtGet("targetSdkVersion", 30)
60
60
  versionCode 22
61
- versionName "10.2.3"
61
+ versionName "11.0.0"
62
62
  }
63
63
  lintOptions {
64
64
  abortOnError false
65
65
  }
66
66
  kotlinOptions {
67
- jvmTarget = '1.8'
67
+ jvmTarget = JavaVersion.VERSION_1_8
68
68
  }
69
69
  }
70
70
 
71
- if (new File(rootProject.projectDir.parentFile, 'package.json').exists()) {
72
- apply from: project(":unimodules-core").file("../unimodules-core.gradle")
73
- } else {
74
- throw new GradleException(
75
- "'unimodules-core.gradle' was not found in the usual React Native dependency location. " +
76
- "This package can only be used in such projects. Are you sure you've installed the dependencies properly?")
77
- }
78
-
79
71
  dependencies {
80
- unimodule "unimodules-core"
81
- unimodule 'expo-modules-core'
72
+ implementation project(':expo-modules-core')
82
73
 
83
74
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.4.21')}"
75
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
76
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1")
84
77
  api 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
85
78
  api "androidx.legacy:legacy-support-v4:1.0.0"
86
79
  api 'commons-codec:commons-codec:1.10'
@@ -23,6 +23,9 @@ object ImagePickerConstants {
23
23
  const val MISSING_URL_MESSAGE = "Intent doesn't contain `url`."
24
24
  const val ERR_CAN_NOT_OPEN_CROP = "ERR_CAN_NOT_OPEN_CROP"
25
25
  const val CAN_NOT_OPEN_CROP_MESSAGE = "Can not open the crop tool."
26
+ const val COROUTINE_CANCELED = "Coroutine canceled by module destruction."
27
+ const val PROMISES_CANCELED = "Module destroyed, all promises canceled."
28
+ const val UNKNOWN_EXCEPTION = "Unknown exception."
26
29
 
27
30
  const val OPTION_QUALITY = "quality"
28
31
  const val OPTION_ALLOWS_EDITING = "allowsEditing"
@@ -32,8 +35,6 @@ object ImagePickerConstants {
32
35
  const val OPTION_EXIF = "exif"
33
36
  const val OPTION_VIDEO_MAX_DURATION = "videoMaxDuration"
34
37
 
35
- // We need to explicitly get latitude, longitude, altitude with their specific accessor functions
36
- // separately so we skip them in this list.
37
38
  val exifTags = arrayOf(
38
39
  arrayOf("string", ExifInterface.TAG_ARTIST),
39
40
  arrayOf("int", ExifInterface.TAG_BITS_PER_SAMPLE),
@@ -123,6 +124,7 @@ object ImagePickerConstants {
123
124
  arrayOf("int", ExifInterface.TAG_SUBJECT_LOCATION),
124
125
  arrayOf("string", ExifInterface.TAG_USER_COMMENT),
125
126
  arrayOf("int", ExifInterface.TAG_WHITE_BALANCE),
127
+ arrayOf("double", ExifInterface.TAG_GPS_ALTITUDE),
126
128
  arrayOf("int", ExifInterface.TAG_GPS_ALTITUDE_REF),
127
129
  arrayOf("string", ExifInterface.TAG_GPS_AREA_INFORMATION),
128
130
  arrayOf("double", ExifInterface.TAG_GPS_DOP),
@@ -136,9 +138,12 @@ object ImagePickerConstants {
136
138
  arrayOf("double", ExifInterface.TAG_GPS_DEST_LONGITUDE),
137
139
  arrayOf("string", ExifInterface.TAG_GPS_DEST_LONGITUDE_REF),
138
140
  arrayOf("int", ExifInterface.TAG_GPS_DIFFERENTIAL),
141
+ arrayOf("string", ExifInterface.TAG_GPS_H_POSITIONING_ERROR),
139
142
  arrayOf("double", ExifInterface.TAG_GPS_IMG_DIRECTION),
140
143
  arrayOf("string", ExifInterface.TAG_GPS_IMG_DIRECTION_REF),
144
+ arrayOf("double", ExifInterface.TAG_GPS_LATITUDE),
141
145
  arrayOf("string", ExifInterface.TAG_GPS_LATITUDE_REF),
146
+ arrayOf("double", ExifInterface.TAG_GPS_LONGITUDE),
142
147
  arrayOf("string", ExifInterface.TAG_GPS_LONGITUDE_REF),
143
148
  arrayOf("string", ExifInterface.TAG_GPS_MAP_DATUM),
144
149
  arrayOf("string", ExifInterface.TAG_GPS_MEASURE_MODE),
@@ -11,6 +11,17 @@ import android.os.Bundle
11
11
  import android.provider.MediaStore
12
12
  import android.util.Log
13
13
  import com.theartofdev.edmodo.cropper.CropImage
14
+ import expo.modules.core.ExportedModule
15
+ import expo.modules.core.ModuleRegistry
16
+ import expo.modules.core.ModuleRegistryDelegate
17
+ import expo.modules.core.Promise
18
+ import expo.modules.core.errors.ModuleDestroyedException
19
+ import expo.modules.core.interfaces.ActivityEventListener
20
+ import expo.modules.core.interfaces.ActivityProvider
21
+ import expo.modules.core.interfaces.ExpoMethod
22
+ import expo.modules.core.interfaces.LifecycleEventListener
23
+ import expo.modules.core.interfaces.services.UIManager
24
+ import expo.modules.core.utilities.FileUtilities.generateOutputPath
14
25
  import expo.modules.imagepicker.ImagePickerOptions.Companion.optionsFromMap
15
26
  import expo.modules.imagepicker.exporters.CompressionImageExporter
16
27
  import expo.modules.imagepicker.exporters.CropImageExporter
@@ -25,16 +36,9 @@ import expo.modules.interfaces.permissions.Permissions
25
36
  import expo.modules.interfaces.permissions.PermissionsResponse
26
37
  import expo.modules.interfaces.permissions.PermissionsResponseListener
27
38
  import expo.modules.interfaces.permissions.PermissionsStatus
28
- import org.unimodules.core.ExportedModule
29
- import org.unimodules.core.ModuleRegistry
30
- import org.unimodules.core.ModuleRegistryDelegate
31
- import org.unimodules.core.Promise
32
- import org.unimodules.core.interfaces.ActivityEventListener
33
- import org.unimodules.core.interfaces.ActivityProvider
34
- import org.unimodules.core.interfaces.ExpoMethod
35
- import org.unimodules.core.interfaces.LifecycleEventListener
36
- import org.unimodules.core.interfaces.services.UIManager
37
- import org.unimodules.core.utilities.FileUtilities.generateOutputPath
39
+ import kotlinx.coroutines.CoroutineScope
40
+ import kotlinx.coroutines.Dispatchers
41
+ import kotlinx.coroutines.cancel
38
42
  import java.io.IOException
39
43
  import java.lang.ref.WeakReference
40
44
 
@@ -47,13 +51,24 @@ class ImagePickerModule(
47
51
  private var mCameraCaptureURI: Uri? = null
48
52
  private var mPromise: Promise? = null
49
53
  private var mPickerOptions: ImagePickerOptions? = null
54
+ private val moduleCoroutineScope = CoroutineScope(Dispatchers.IO)
55
+ private var exifDataHandler: ExifDataHandler? = null
56
+
57
+ override fun onDestroy() {
58
+ try {
59
+ mUIManager.unregisterLifecycleEventListener(this)
60
+ moduleCoroutineScope.cancel(ModuleDestroyedException(ImagePickerConstants.PROMISES_CANCELED))
61
+ } catch (e: IllegalStateException) {
62
+ Log.e(ImagePickerConstants.TAG, "The scope does not have a job in it")
63
+ }
64
+ }
50
65
 
51
66
  /**
52
67
  * Android system sometimes kills the `MainActivity` after the `ImagePicker` finishes.
53
68
  * Moreover, the react context will be reloaded again in such a case. We need to handle this situation.
54
69
  * To do it we track if the current activity was destroyed.
55
70
  */
56
- private var mWasDestroyed = false
71
+ private var mWasHostDestroyed = false
57
72
 
58
73
  private val mImageLoader: ImageLoaderInterface by moduleRegistry()
59
74
  private val mUIManager: UIManager by moduleRegistry()
@@ -126,7 +141,8 @@ class ImagePickerModule(
126
141
 
127
142
  val permissionsResponseHandler = PermissionsResponseListener { permissionsResponse: Map<String, PermissionsResponse> ->
128
143
  if (permissionsResponse[Manifest.permission.WRITE_EXTERNAL_STORAGE]?.status == PermissionsStatus.GRANTED &&
129
- permissionsResponse[Manifest.permission.CAMERA]?.status == PermissionsStatus.GRANTED) {
144
+ permissionsResponse[Manifest.permission.CAMERA]?.status == PermissionsStatus.GRANTED
145
+ ) {
130
146
  launchCameraWithPermissionsGranted(promise, cameraIntent, pickerOptions)
131
147
  } else {
132
148
  promise.reject(SecurityException("User rejected permissions"))
@@ -255,7 +271,7 @@ class ImagePickerModule(
255
271
  setOutputCompressFormat(compressFormat)
256
272
  setOutputCompressQuality(pickerOptions.quality)
257
273
  }
258
-
274
+ exifDataHandler = ExifDataHandler(uri)
259
275
  startActivityOnResult(cropImageBuilder.getIntent(context), CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE, promise, pickerOptions)
260
276
  }
261
277
 
@@ -270,7 +286,7 @@ class ImagePickerModule(
270
286
  mUIManager.unregisterActivityEventListener(this)
271
287
 
272
288
  var pickerOptions = mPickerOptions!!
273
- val promise = if (mWasDestroyed && mPromise !is PendingPromise) {
289
+ val promise = if (mWasHostDestroyed && mPromise !is PendingPromise) {
274
290
  if (pickerOptions.isBase64) {
275
291
  // we know that the activity was killed and we don't want to store
276
292
  // base64 into `SharedPreferences`...
@@ -323,14 +339,16 @@ class ImagePickerModule(
323
339
  mPickerOptions != null &&
324
340
  // When we launched the crop tool and the android kills current activity, the references can be different.
325
341
  // So, we fallback to the requestCode in this case.
326
- (activity === experienceActivity || mWasDestroyed && requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE)
342
+ (activity === experienceActivity || mWasHostDestroyed && requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE)
327
343
  }
328
344
 
329
345
  private fun handleOnActivityResult(promise: Promise, activity: Activity, requestCode: Int, resultCode: Int, intent: Intent?, pickerOptions: ImagePickerOptions) {
330
346
  if (resultCode != Activity.RESULT_OK) {
331
- promise.resolve(Bundle().apply {
332
- putBoolean("cancelled", true)
333
- })
347
+ promise.resolve(
348
+ Bundle().apply {
349
+ putBoolean("cancelled", true)
350
+ }
351
+ )
334
352
  return
335
353
  }
336
354
 
@@ -339,7 +357,17 @@ class ImagePickerModule(
339
357
  if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
340
358
  val result = CropImage.getActivityResult(intent)
341
359
  val exporter = CropImageExporter(result.rotation, result.cropRect, pickerOptions.isBase64)
342
- ImageResultTask(promise, result.uri, contentResolver, CropFileProvider(result.uri), pickerOptions.isExif, exporter).execute()
360
+ ImageResultTask(
361
+ promise,
362
+ result.uri,
363
+ contentResolver,
364
+ CropFileProvider(result.uri),
365
+ pickerOptions.isAllowsEditing,
366
+ pickerOptions.isExif,
367
+ exporter,
368
+ exifDataHandler,
369
+ moduleCoroutineScope
370
+ ).execute()
343
371
  return
344
372
  }
345
373
 
@@ -368,7 +396,17 @@ class ImagePickerModule(
368
396
  CompressionImageExporter(mImageLoader, pickerOptions.quality, pickerOptions.isBase64)
369
397
  }
370
398
 
371
- ImageResultTask(promise, uri, contentResolver, CacheFileProvider(mContext.cacheDir, deduceExtension(type)), pickerOptions.isExif, exporter).execute()
399
+ ImageResultTask(
400
+ promise,
401
+ uri,
402
+ contentResolver,
403
+ CacheFileProvider(mContext.cacheDir, deduceExtension(type)),
404
+ pickerOptions.isAllowsEditing,
405
+ pickerOptions.isExif,
406
+ exporter,
407
+ exifDataHandler,
408
+ moduleCoroutineScope
409
+ ).execute()
372
410
  return
373
411
  }
374
412
 
@@ -376,7 +414,7 @@ class ImagePickerModule(
376
414
  val metadataRetriever = MediaMetadataRetriever().apply {
377
415
  setDataSource(mContext, uri)
378
416
  }
379
- VideoResultTask(promise, uri, contentResolver, CacheFileProvider(mContext.cacheDir, ".mp4"), metadataRetriever).execute()
417
+ VideoResultTask(promise, uri, contentResolver, CacheFileProvider(mContext.cacheDir, ".mp4"), metadataRetriever, moduleCoroutineScope).execute()
380
418
  } catch (e: RuntimeException) {
381
419
  e.printStackTrace()
382
420
  promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
@@ -389,11 +427,15 @@ class ImagePickerModule(
389
427
  //region LifecycleEventListener
390
428
 
391
429
  override fun onHostDestroy() {
392
- mWasDestroyed = true
393
- mUIManager.unregisterLifecycleEventListener(this)
430
+ mWasHostDestroyed = true
394
431
  }
395
432
 
396
- override fun onHostResume() = Unit
433
+ override fun onHostResume() {
434
+ if (mWasHostDestroyed) {
435
+ _experienceActivity = WeakReference(mActivityProvider.currentActivity)
436
+ mWasHostDestroyed = false
437
+ }
438
+ }
397
439
  override fun onHostPause() = Unit
398
440
 
399
441
  //endregion
@@ -1,6 +1,6 @@
1
1
  package expo.modules.imagepicker
2
2
 
3
- import org.unimodules.core.Promise
3
+ import expo.modules.core.Promise
4
4
 
5
5
  data class ImagePickerOptions(
6
6
  val quality: Int,
@@ -1,7 +1,7 @@
1
1
  package expo.modules.imagepicker
2
2
 
3
3
  import android.content.Context
4
- import org.unimodules.core.BasePackage
4
+ import expo.modules.core.BasePackage
5
5
 
6
6
  class ImagePickerPackage : BasePackage() {
7
7
  override fun createExportedModules(context: Context) = listOf(ImagePickerModule(context))
@@ -6,7 +6,8 @@ import android.net.Uri
6
6
  import android.util.Log
7
7
  import android.webkit.MimeTypeMap
8
8
  import androidx.core.content.FileProvider
9
- import org.unimodules.core.utilities.FileUtilities.generateOutputPath
9
+ import androidx.exifinterface.media.ExifInterface
10
+ import expo.modules.core.utilities.FileUtilities.generateOutputPath
10
11
  import java.io.File
11
12
  import java.io.IOException
12
13
 
@@ -63,3 +64,30 @@ fun deduceExtension(type: String): String = when {
63
64
  }
64
65
  else -> ".jpg"
65
66
  }
67
+
68
+ class ExifDataHandler(private val uri: Uri) {
69
+ fun copyExifData(newUri: Uri, contentResolver: ContentResolver) {
70
+ if (uri == newUri) {
71
+ return
72
+ }
73
+ contentResolver.openInputStream(uri)?.use { input ->
74
+ val oldExif = ExifInterface(input)
75
+ newUri.path?.let {
76
+ val newExif = ExifInterface(it)
77
+ for ((_, exifTag) in ImagePickerConstants.exifTags) {
78
+ val value = oldExif.getAttribute(exifTag)
79
+ if (value != null &&
80
+ exifTag != ExifInterface.TAG_IMAGE_LENGTH &&
81
+ exifTag != ExifInterface.TAG_IMAGE_WIDTH &&
82
+ exifTag != ExifInterface.TAG_PIXEL_X_DIMENSION &&
83
+ exifTag != ExifInterface.TAG_PIXEL_Y_DIMENSION &&
84
+ exifTag != ExifInterface.TAG_ORIENTATION
85
+ ) {
86
+ newExif.setAttribute(exifTag, value)
87
+ }
88
+ }
89
+ newExif.saveAttributes()
90
+ }
91
+ }
92
+ }
93
+ }
@@ -1,7 +1,7 @@
1
1
  package expo.modules.imagepicker
2
2
 
3
3
  import android.os.Bundle
4
- import org.unimodules.core.Promise
4
+ import expo.modules.core.Promise
5
5
 
6
6
  /**
7
7
  * Class that represents a promise, which will not resolve immediately but saves results to the [PickerResultsStore].
@@ -25,12 +25,14 @@ class PendingPromise(
25
25
  }
26
26
 
27
27
  override fun reject(code: String, message: String, e: Throwable?) {
28
- pickerResultsStore.addPendingResult(Bundle().apply {
29
- putString("code", code)
30
- putString("message", message)
31
- e?.let {
32
- putString("exception", it.toString())
28
+ pickerResultsStore.addPendingResult(
29
+ Bundle().apply {
30
+ putString("code", code)
31
+ putString("message", message)
32
+ e?.let {
33
+ putString("exception", it.toString())
34
+ }
33
35
  }
34
- })
36
+ )
35
37
  }
36
38
  }
@@ -1,7 +1,7 @@
1
1
  package expo.modules.imagepicker.fileproviders
2
2
 
3
3
  import expo.modules.imagepicker.ImagePickerConstants
4
- import org.unimodules.core.utilities.FileUtilities
4
+ import expo.modules.core.utilities.FileUtilities
5
5
  import java.io.File
6
6
 
7
7
  class CacheFileProvider(
@@ -2,64 +2,106 @@ package expo.modules.imagepicker.tasks
2
2
 
3
3
  import android.content.ContentResolver
4
4
  import android.net.Uri
5
- import android.os.AsyncTask
6
5
  import android.os.Bundle
7
6
  import android.util.Base64
7
+ import android.util.Log
8
8
  import androidx.exifinterface.media.ExifInterface
9
+ import expo.modules.core.Promise
10
+ import expo.modules.core.errors.ModuleDestroyedException
11
+ import expo.modules.imagepicker.ExifDataHandler
9
12
  import expo.modules.imagepicker.ImagePickerConstants
10
13
  import expo.modules.imagepicker.ImagePickerConstants.exifTags
11
14
  import expo.modules.imagepicker.exporters.ImageExporter
12
15
  import expo.modules.imagepicker.exporters.ImageExporter.Listener
13
16
  import expo.modules.imagepicker.fileproviders.FileProvider
14
- import org.unimodules.core.Promise
17
+ import kotlinx.coroutines.CoroutineScope
18
+ import kotlinx.coroutines.launch
19
+ import kotlinx.coroutines.suspendCancellableCoroutine
15
20
  import java.io.ByteArrayOutputStream
21
+ import java.io.File
16
22
  import java.io.IOException
23
+ import kotlin.coroutines.resume
24
+ import kotlin.coroutines.resumeWithException
17
25
 
18
26
  open class ImageResultTask(
19
27
  private val promise: Promise,
20
28
  private val uri: Uri,
21
29
  private val contentResolver: ContentResolver,
22
30
  private val fileProvider: FileProvider,
31
+ private val isEdited: Boolean,
23
32
  private val withExifData: Boolean,
24
- private val imageExporter: ImageExporter
25
- ) :
26
- AsyncTask<Void?, Void?, Void?>() {
27
-
28
- override fun doInBackground(vararg params: Void?): Void? {
33
+ private val imageExporter: ImageExporter,
34
+ private var exifDataHandler: ExifDataHandler?,
35
+ private val coroutineScope: CoroutineScope
36
+ ) {
37
+ /**
38
+ * We need to make coroutine wait till the file is generated, while the underlying
39
+ * thread is free to continue executing other coroutines.
40
+ */
41
+ private suspend fun getFile(): File = suspendCancellableCoroutine { cancellableContinuation ->
29
42
  try {
30
43
  val outputFile = fileProvider.generateFile()
31
- val exif: Bundle? = if (withExifData) readExif() else null
44
+ cancellableContinuation.resume(outputFile)
45
+ } catch (e: Exception) {
46
+ cancellableContinuation.resumeWithException(e)
47
+ }
48
+ }
32
49
 
33
- val imageExporterHandler = object : Listener {
34
- override fun onResult(out: ByteArrayOutputStream?, width: Int, height: Int) {
35
- val response = Bundle().apply {
36
- putString("uri", Uri.fromFile(outputFile).toString())
37
- putInt("width", width)
38
- putInt("height", height)
39
- putBoolean("cancelled", false)
40
- putString("type", "image")
50
+ /**
51
+ * We need to make coroutine wait till the exif data is being read, while the underlying
52
+ * thread is free to continue executing other coroutines.
53
+ */
54
+ private suspend fun getExifData(): Bundle? = suspendCancellableCoroutine { cancellableContinuation ->
55
+ try {
56
+ val exif = if (withExifData) readExif() else null
57
+ cancellableContinuation.resume(exif)
58
+ } catch (e: Exception) {
59
+ cancellableContinuation.resumeWithException(e)
60
+ }
61
+ }
41
62
 
42
- out?.let {
43
- putString("base64", Base64.encodeToString(it.toByteArray(), Base64.NO_WRAP))
44
- }
45
- exif?.let {
46
- putBundle("exif", it)
63
+ fun execute() {
64
+ coroutineScope.launch {
65
+ try {
66
+ val outputFile = getFile()
67
+ if (isEdited) {
68
+ exifDataHandler?.copyExifData(uri, contentResolver)
69
+ }
70
+ val exif = getExifData()
71
+ val imageExporterHandler = object : Listener {
72
+ override fun onResult(out: ByteArrayOutputStream?, width: Int, height: Int) {
73
+ val response = Bundle().apply {
74
+ putString("uri", Uri.fromFile(outputFile).toString())
75
+ putInt("width", width)
76
+ putInt("height", height)
77
+ putBoolean("cancelled", false)
78
+ putString("type", "image")
79
+
80
+ out?.let {
81
+ putString("base64", Base64.encodeToString(it.toByteArray(), Base64.NO_WRAP))
82
+ }
83
+ exif?.let {
84
+ putBundle("exif", it)
85
+ }
47
86
  }
87
+ promise.resolve(response)
48
88
  }
49
- promise.resolve(response)
50
- }
51
89
 
52
- override fun onFailure(cause: Throwable?) {
53
- promise.reject(ImagePickerConstants.ERR_CAN_NOT_SAVE_RESULT, ImagePickerConstants.CAN_NOT_SAVE_RESULT_MESSAGE, cause)
90
+ override fun onFailure(cause: Throwable?) {
91
+ promise.reject(ImagePickerConstants.ERR_CAN_NOT_SAVE_RESULT, ImagePickerConstants.CAN_NOT_SAVE_RESULT_MESSAGE, cause)
92
+ }
54
93
  }
94
+ imageExporter.export(uri, outputFile, imageExporterHandler)
95
+ } catch (e: ModuleDestroyedException) {
96
+ Log.i(ImagePickerConstants.TAG, ImagePickerConstants.COROUTINE_CANCELED, e)
97
+ promise.reject(ImagePickerConstants.COROUTINE_CANCELED, e)
98
+ } catch (e: IOException) {
99
+ promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
100
+ } catch (e: Exception) {
101
+ Log.e(ImagePickerConstants.TAG, ImagePickerConstants.UNKNOWN_EXCEPTION, e)
102
+ promise.reject(ImagePickerConstants.UNKNOWN_EXCEPTION, e)
55
103
  }
56
-
57
- imageExporter.export(uri, outputFile, imageExporterHandler)
58
- } catch (e: IOException) {
59
- promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
60
104
  }
61
-
62
- return null
63
105
  }
64
106
 
65
107
  @Throws(IOException::class)
@@ -3,49 +3,76 @@ package expo.modules.imagepicker.tasks
3
3
  import android.content.ContentResolver
4
4
  import android.media.MediaMetadataRetriever
5
5
  import android.net.Uri
6
- import android.os.AsyncTask
7
6
  import android.os.Bundle
7
+ import android.util.Log
8
+ import expo.modules.core.Promise
9
+ import expo.modules.core.errors.ModuleDestroyedException
8
10
  import expo.modules.imagepicker.ImagePickerConstants
9
11
  import expo.modules.imagepicker.fileproviders.FileProvider
10
- import org.unimodules.core.Promise
12
+ import kotlinx.coroutines.CoroutineScope
13
+ import kotlinx.coroutines.launch
14
+ import kotlinx.coroutines.suspendCancellableCoroutine
11
15
  import java.io.File
12
16
  import java.io.FileOutputStream
13
17
  import java.io.IOException
14
- import java.lang.NullPointerException
18
+ import kotlin.coroutines.resume
19
+ import kotlin.coroutines.resumeWithException
15
20
 
16
21
  class VideoResultTask(
17
22
  private val promise: Promise,
18
23
  private val uri: Uri,
19
24
  private val contentResolver: ContentResolver,
20
25
  private val fileProvider: FileProvider,
21
- private val mediaMetadataRetriever: MediaMetadataRetriever
22
- ) :
23
- AsyncTask<Void?, Void?, Void?>() {
26
+ private val mediaMetadataRetriever: MediaMetadataRetriever,
27
+ private val coroutineScope: CoroutineScope
28
+ ) {
29
+ private fun extractMediaMetadata(key: Int): Int =
30
+ mediaMetadataRetriever.extractMetadata(key)!!.toInt()
24
31
 
25
- override fun doInBackground(vararg params: Void?): Void? {
32
+ /**
33
+ * We need to make coroutine wait till the video is saved, while the underlying
34
+ * thread is free to continue executing other coroutines.
35
+ */
36
+ private suspend fun getFile(): File = suspendCancellableCoroutine { cancellableContinuation ->
26
37
  try {
27
38
  val outputFile = fileProvider.generateFile()
28
39
  saveVideo(outputFile)
29
- val response = Bundle().apply {
30
- putString("uri", Uri.fromFile(outputFile).toString())
31
- putBoolean("cancelled", false)
32
- putString("type", "video")
33
- putInt("width", mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt())
34
- putInt("height", mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt())
35
- putInt("rotation", mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)!!.toInt())
36
- putInt("duration", mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!.toInt())
40
+ cancellableContinuation.resume(outputFile)
41
+ } catch (e: Exception) {
42
+ cancellableContinuation.resumeWithException(e)
43
+ }
44
+ }
45
+
46
+ fun execute() {
47
+ coroutineScope.launch {
48
+ try {
49
+ val outputFile = getFile()
50
+ val response = Bundle().apply {
51
+ putString("uri", Uri.fromFile(outputFile).toString())
52
+ putBoolean("cancelled", false)
53
+ putString("type", "video")
54
+ putInt("width", extractMediaMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH))
55
+ putInt("height", extractMediaMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT))
56
+ putInt("rotation", extractMediaMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION))
57
+ putInt("duration", extractMediaMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION))
58
+ }
59
+ promise.resolve(response)
60
+ } catch (e: ModuleDestroyedException) {
61
+ Log.d(ImagePickerConstants.TAG, ImagePickerConstants.COROUTINE_CANCELED, e)
62
+ promise.reject(e)
63
+ } catch (e: NullPointerException) {
64
+ promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
65
+ } catch (e: IllegalArgumentException) {
66
+ promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
67
+ } catch (e: SecurityException) {
68
+ promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
69
+ } catch (e: IOException) {
70
+ promise.reject(ImagePickerConstants.ERR_CAN_NOT_SAVE_RESULT, ImagePickerConstants.CAN_NOT_SAVE_RESULT_MESSAGE, e)
71
+ } catch (e: Exception) {
72
+ Log.e(ImagePickerConstants.TAG, ImagePickerConstants.UNKNOWN_EXCEPTION, e)
73
+ promise.reject(ImagePickerConstants.UNKNOWN_EXCEPTION, e)
37
74
  }
38
- promise.resolve(response)
39
- } catch (e: NullPointerException) {
40
- promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
41
- } catch (e: IllegalArgumentException) {
42
- promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
43
- } catch (e: SecurityException) {
44
- promise.reject(ImagePickerConstants.ERR_CAN_NOT_EXTRACT_METADATA, ImagePickerConstants.CAN_NOT_EXTRACT_METADATA_MESSAGE, e)
45
- } catch (e: IOException) {
46
- promise.reject(ImagePickerConstants.ERR_CAN_NOT_SAVE_RESULT, ImagePickerConstants.CAN_NOT_SAVE_RESULT_MESSAGE, e)
47
75
  }
48
- return null
49
76
  }
50
77
 
51
78
  @Throws(IOException::class)
@@ -1,2 +1,2 @@
1
- declare const _default: import("@unimodules/core").ProxyNativeModule;
1
+ declare const _default: import("expo-modules-core").ProxyNativeModule;
2
2
  export default _default;
@@ -1,3 +1,3 @@
1
- import { NativeModulesProxy } from '@unimodules/core';
1
+ import { NativeModulesProxy } from 'expo-modules-core';
2
2
  export default NativeModulesProxy.ExponentImagePicker;
3
3
  //# sourceMappingURL=ExponentImagePicker.js.map