expo-image-picker 14.3.1 → 14.4.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/CHANGELOG.md CHANGED
@@ -10,7 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 14.3.1 — 2023-07-04
13
+ ## 14.4.0 — 2023-07-28
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 14.3.2 - 2023-07-23
18
+
19
+ ### 💡 Others
20
+
21
+ - On Android, restore behavior from [#22658](https://github.com/expo/expo/pull/22658). ([#23617](https://github.com/expo/expo/pull/23617) by [@alanhughes](https://github.com/alanjhughes)) ([#22658](https://github.com/expo/expo/pull/22658), [#23617](https://github.com/expo/expo/pull/23617) by [@alanjhughes](https://github.com/alanjhughes))
22
+
23
+ ## 14.3.1 - 2023-07-04
14
24
 
15
25
  ### 🐛 Bug fixes
16
26
 
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '14.3.1'
6
+ version = '14.4.0'
7
7
 
8
8
  buildscript {
9
9
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
@@ -67,7 +67,7 @@ android {
67
67
  minSdkVersion safeExtGet("minSdkVersion", 21)
68
68
  targetSdkVersion safeExtGet("targetSdkVersion", 33)
69
69
  versionCode 22
70
- versionName "14.3.1"
70
+ versionName "14.4.0"
71
71
  }
72
72
  lintOptions {
73
73
  abortOnError false
@@ -82,8 +82,8 @@ android {
82
82
  dependencies {
83
83
  implementation project(':expo-modules-core')
84
84
 
85
- implementation "androidx.activity:activity-ktx:1.7.1"
86
- implementation "androidx.appcompat:appcompat:1.4.2"
85
+ implementation "androidx.activity:activity-ktx:1.7.2"
86
+ implementation "androidx.appcompat:appcompat:1.6.1"
87
87
  implementation "androidx.exifinterface:exifinterface:1.3.3"
88
88
  implementation "com.github.CanHub:Android-Image-Cropper:4.3.1"
89
89
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
@@ -1,37 +1,51 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
- <!-- Required for picking images from camera directly -->
3
- <uses-permission android:name="android.permission.CAMERA"/>
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ xmlns:tools="http://schemas.android.com/tools">
3
+ <!-- Required for picking images from camera directly -->
4
+ <uses-permission android:name="android.permission.CAMERA" />
4
5
 
5
- <!-- Required for picking images from camera roll -->
6
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
7
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
8
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
9
- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
6
+ <!-- Required for picking images from camera roll -->
7
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
8
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
9
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
10
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
10
11
 
11
- <application>
12
- <activity
13
- android:name="com.canhub.cropper.CropImageActivity"
14
- android:theme="@style/Base.Theme.AppCompat"/>
15
- <!-- https://developer.android.com/guide/topics/manifest/provider-element.html -->
16
- <provider
17
- android:name=".fileprovider.ImagePickerFileProvider"
18
- android:authorities="${applicationId}.ImagePickerFileProvider"
19
- android:exported="false"
20
- android:grantUriPermissions="true">
21
- <meta-data
22
- android:name="android.support.FILE_PROVIDER_PATHS"
23
- android:resource="@xml/image_picker_provider_paths"/>
24
- </provider>
25
- </application>
12
+ <application>
13
+ <service
14
+ android:name="com.google.android.gms.metadata.ModuleDependencies"
15
+ android:enabled="false"
16
+ android:exported="false"
17
+ tools:ignore="MissingClass">
18
+ <intent-filter>
19
+ <action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
20
+ </intent-filter>
21
+ <meta-data
22
+ android:name="photopicker_activity:0:required"
23
+ android:value="" />
24
+ </service>
26
25
 
27
- <queries>
28
- <intent>
29
- <!-- Required for picking images from the camera roll if targeting API 30 -->
30
- <action android:name="android.media.action.IMAGE_CAPTURE" />
31
- </intent>
32
- <intent>
33
- <!-- Required for picking images from the camera if targeting API 30 -->
34
- <action android:name="android.media.action.ACTION_VIDEO_CAPTURE" />
35
- </intent>
36
- </queries>
26
+ <activity
27
+ android:name="com.canhub.cropper.CropImageActivity"
28
+ android:theme="@style/Base.Theme.AppCompat" />
29
+ <!-- https://developer.android.com/guide/topics/manifest/provider-element.html -->
30
+ <provider
31
+ android:name=".fileprovider.ImagePickerFileProvider"
32
+ android:authorities="${applicationId}.ImagePickerFileProvider"
33
+ android:exported="false"
34
+ android:grantUriPermissions="true">
35
+ <meta-data
36
+ android:name="android.support.FILE_PROVIDER_PATHS"
37
+ android:resource="@xml/image_picker_provider_paths" />
38
+ </provider>
39
+ </application>
40
+
41
+ <queries>
42
+ <intent>
43
+ <!-- Required for picking images from the camera roll if targeting API 30 -->
44
+ <action android:name="android.media.action.IMAGE_CAPTURE" />
45
+ </intent>
46
+ <intent>
47
+ <!-- Required for picking images from the camera if targeting API 30 -->
48
+ <action android:name="android.media.action.ACTION_VIDEO_CAPTURE" />
49
+ </intent>
50
+ </queries>
37
51
  </manifest>
@@ -10,6 +10,9 @@ internal class FailedToDeduceTypeException :
10
10
  internal class FailedToCreateFileException(path: String, cause: Throwable? = null) :
11
11
  CodedException("Failed to create the file '$path'", cause)
12
12
 
13
+ internal class FailedToPickMediaException :
14
+ CodedException("Failed to parse PhotoPicker result")
15
+
13
16
  internal class FailedToExtractVideoMetadataException(file: File? = null, cause: Throwable? = null) :
14
17
  CodedException("Failed to extract metadata from video file '${file?.toUri()?.toString() ?: ""}'", cause)
15
18
 
@@ -166,6 +166,7 @@ class ImagePickerModule : Module() {
166
166
  when (val pickingResult = pickerLauncher()) {
167
167
  is ImagePickerContractResult.Success -> pickingResult
168
168
  is ImagePickerContractResult.Cancelled -> throw OperationCanceledException()
169
+ is ImagePickerContractResult.Error -> throw FailedToPickMediaException()
169
170
  }
170
171
  }
171
172
 
@@ -23,7 +23,7 @@ import java.io.Serializable
23
23
  internal class CameraContract(
24
24
  private val appContextProvider: AppContextProvider,
25
25
  ) : AppContextActivityResultContract<CameraContractOptions, ImagePickerContractResult> {
26
- val contentResolver: ContentResolver
26
+ private val contentResolver: ContentResolver
27
27
  get() = requireNotNull(appContextProvider.appContext.reactContext) {
28
28
  "React Application Context is null"
29
29
  }.contentResolver
@@ -7,6 +7,7 @@ import expo.modules.imagepicker.MediaType
7
7
  * Data required to be returned upon successful contract completion
8
8
  */
9
9
  internal sealed class ImagePickerContractResult private constructor() {
10
- class Cancelled : ImagePickerContractResult()
11
10
  class Success(val data: List<Pair<MediaType, Uri>>) : ImagePickerContractResult()
11
+ class Cancelled : ImagePickerContractResult()
12
+ class Error : ImagePickerContractResult()
12
13
  }
@@ -4,6 +4,7 @@ import android.app.Activity
4
4
  import android.content.Context
5
5
  import android.content.Intent
6
6
  import android.net.Uri
7
+ import android.os.Build
7
8
  import androidx.core.net.toFile
8
9
  import androidx.core.net.toUri
9
10
  import androidx.core.os.bundleOf
@@ -54,7 +55,11 @@ internal class CropImageContract(
54
55
  }
55
56
 
56
57
  override fun parseResult(input: CropImageContractOptions, resultCode: Int, intent: Intent?): ImagePickerContractResult {
57
- val result = intent?.getParcelableExtra<CropImage.ActivityResult?>(CropImage.CROP_IMAGE_EXTRA_RESULT)
58
+ val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
59
+ intent?.getParcelableExtra(CropImage.CROP_IMAGE_EXTRA_RESULT, CropImage.ActivityResult::class.java)
60
+ } else {
61
+ intent?.getParcelableExtra(CropImage.CROP_IMAGE_EXTRA_RESULT)
62
+ }
58
63
  if (resultCode == Activity.RESULT_CANCELED || result == null) {
59
64
  return ImagePickerContractResult.Cancelled()
60
65
  }
@@ -27,75 +27,77 @@ import java.io.Serializable
27
27
  internal class ImageLibraryContract(
28
28
  private val appContextProvider: AppContextProvider,
29
29
  ) : AppContextActivityResultContract<ImageLibraryContractOptions, ImagePickerContractResult> {
30
- val contentResolver: ContentResolver
30
+ private val contentResolver: ContentResolver
31
31
  get() = requireNotNull(appContextProvider.appContext.reactContext) {
32
32
  "React Application Context is null"
33
33
  }.contentResolver
34
34
 
35
35
  override fun createIntent(context: Context, input: ImageLibraryContractOptions): Intent {
36
- if (PickVisualMedia.isPhotoPickerAvailable(context)) {
37
- val request = PickVisualMediaRequest.Builder()
38
- .setMediaType(
39
- when (input.options.mediaTypes) {
40
- MediaTypes.VIDEOS -> {
41
- PickVisualMedia.VideoOnly
42
- }
43
-
44
- MediaTypes.IMAGES -> {
45
- PickVisualMedia.ImageOnly
46
- }
47
-
48
- else -> {
49
- PickVisualMedia.ImageAndVideo
50
- }
36
+ val request = PickVisualMediaRequest.Builder()
37
+ .setMediaType(
38
+ when (input.options.mediaTypes) {
39
+ MediaTypes.VIDEOS -> {
40
+ PickVisualMedia.VideoOnly
51
41
  }
52
- ).build()
53
42
 
54
- if (input.options.allowsMultipleSelection) {
55
- val selectionLimit = input.options.selectionLimit
43
+ MediaTypes.IMAGES -> {
44
+ PickVisualMedia.ImageOnly
45
+ }
56
46
 
57
- if (selectionLimit == 1) {
58
- // If multiple selection is allowed but the limit is 1, we should ignore
59
- // the multiple selection flag and just treat it as a single selection.
60
- return PickVisualMedia().createIntent(context, request)
47
+ else -> {
48
+ PickVisualMedia.ImageAndVideo
49
+ }
61
50
  }
51
+ )
52
+ .build()
62
53
 
63
- if (selectionLimit > 1) {
64
- return PickMultipleVisualMedia(selectionLimit).createIntent(context, request)
65
- }
54
+ if (input.options.allowsMultipleSelection) {
55
+ val selectionLimit = input.options.selectionLimit
66
56
 
67
- // If the selection limit is 0, it is the same as unlimited selection.
68
- if (selectionLimit == UNLIMITED_SELECTION) {
69
- return PickMultipleVisualMedia().createIntent(context, request)
70
- }
57
+ if (selectionLimit == 1) {
58
+ // If multiple selection is allowed but the limit is 1, we should ignore
59
+ // the multiple selection flag and just treat it as a single selection.
60
+ return PickVisualMedia().createIntent(context, request)
71
61
  }
72
62
 
73
- return PickVisualMedia().createIntent(context, request)
74
- }
63
+ if (selectionLimit > 1) {
64
+ return PickMultipleVisualMedia(selectionLimit).createIntent(context, request)
65
+ }
75
66
 
76
- return Intent(Intent.ACTION_GET_CONTENT)
77
- .addCategory(Intent.CATEGORY_OPENABLE)
78
- .setType(input.options.mediaTypes.toMimeType())
79
- .apply {
80
- if (input.options.allowsMultipleSelection) {
81
- putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
82
- }
83
- if (input.options.mediaTypes == MediaTypes.ALL) {
84
- putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(MediaTypes.IMAGES.toMimeType(), MediaTypes.VIDEOS.toMimeType()))
85
- }
67
+ // If the selection limit is 0, it is the same as unlimited selection.
68
+ if (selectionLimit == UNLIMITED_SELECTION) {
69
+ return PickMultipleVisualMedia().createIntent(context, request)
86
70
  }
71
+ }
72
+
73
+ return PickVisualMedia().createIntent(context, request)
87
74
  }
88
75
 
89
76
  override fun parseResult(input: ImageLibraryContractOptions, resultCode: Int, intent: Intent?) =
90
77
  if (resultCode == Activity.RESULT_CANCELED) {
91
78
  ImagePickerContractResult.Cancelled()
92
- } else if (input.options.allowsMultipleSelection) {
93
- val uris = requireNotNull(intent).getAllDataUris()
94
- ImagePickerContractResult.Success(uris.map { uri -> uri.toMediaType(contentResolver) to uri })
95
79
  } else {
96
- val uri = requireNotNull(requireNotNull(intent).data)
97
- val type = uri.toMediaType(contentResolver)
98
- ImagePickerContractResult.Success(listOf(type to uri))
80
+ intent?.takeIf { resultCode == Activity.RESULT_OK }?.getAllDataUris()?.let { uris ->
81
+ if (input.options.allowsMultipleSelection) {
82
+ ImagePickerContractResult.Success(
83
+ uris.map { uri ->
84
+ uri.toMediaType(contentResolver) to uri
85
+ }
86
+ )
87
+ } else {
88
+ if (intent.data != null) {
89
+ intent.data?.let {
90
+ val type = it.toMediaType(contentResolver)
91
+ ImagePickerContractResult.Success(listOf(type to it))
92
+ }
93
+ } else {
94
+ uris.firstOrNull()?.let { uri ->
95
+ val type = uri.toMediaType(contentResolver)
96
+ ImagePickerContractResult.Success(listOf(type to uri))
97
+ } ?: ImagePickerContractResult.Error()
98
+ }
99
+ }
100
+ } ?: ImagePickerContractResult.Error()
99
101
  }
100
102
  }
101
103
 
@@ -1,3 +1,3 @@
1
- declare const _default: import("expo-modules-core").ProxyNativeModule;
1
+ declare const _default: any;
2
2
  export default _default;
3
3
  //# sourceMappingURL=ExponentImagePicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExponentImagePicker.d.ts","sourceRoot":"","sources":["../src/ExponentImagePicker.ts"],"names":[],"mappings":";AACA,wBAAsD"}
1
+ {"version":3,"file":"ExponentImagePicker.d.ts","sourceRoot":"","sources":["../src/ExponentImagePicker.ts"],"names":[],"mappings":";AACA,wBAA0D"}
@@ -1,3 +1,3 @@
1
- import { NativeModulesProxy } from 'expo-modules-core';
2
- export default NativeModulesProxy.ExponentImagePicker;
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ export default requireNativeModule('ExponentImagePicker');
3
3
  //# sourceMappingURL=ExponentImagePicker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExponentImagePicker.js","sourceRoot":"","sources":["../src/ExponentImagePicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,eAAe,kBAAkB,CAAC,mBAAmB,CAAC","sourcesContent":["import { NativeModulesProxy } from 'expo-modules-core';\nexport default NativeModulesProxy.ExponentImagePicker;\n"]}
1
+ {"version":3,"file":"ExponentImagePicker.js","sourceRoot":"","sources":["../src/ExponentImagePicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,eAAe,mBAAmB,CAAC,qBAAqB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\nexport default requireNativeModule('ExponentImagePicker');\n"]}
@@ -102,6 +102,7 @@ public class ImagePickerModule: Module, OnMediaPickingResultHandler {
102
102
  let options = pickingContext.options
103
103
 
104
104
  let picker = UIImagePickerController()
105
+ picker.fixCannotMoveEditingBox()
105
106
 
106
107
  if sourceType == .camera {
107
108
  #if targetEnvironment(simulator)
@@ -0,0 +1,58 @@
1
+ // Copyright 2016-present 650 Industries. All rights reserved.
2
+
3
+ extension UIImagePickerController {
4
+ func fixCannotMoveEditingBox() {
5
+ if let cropView = cropView,
6
+ let scrollView = scrollView,
7
+ scrollView.contentOffset.y == 0
8
+ {
9
+ let top = cropView.frame.minY + self.view.safeAreaInsets.top
10
+ let bottom = scrollView.frame.height - cropView.frame.height - top
11
+ scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
12
+
13
+ var offset: CGFloat = 0
14
+ if scrollView.contentSize.height > scrollView.contentSize.width {
15
+ offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
16
+ }
17
+ scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
18
+ }
19
+
20
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
21
+ self?.fixCannotMoveEditingBox()
22
+ }
23
+ }
24
+
25
+ var cropView: UIView? {
26
+ return findCropView(from: self.view)
27
+ }
28
+
29
+ var scrollView: UIScrollView? {
30
+ return findScrollView(from: self.view)
31
+ }
32
+
33
+ func findCropView(from view: UIView) -> UIView? {
34
+ let width = UIScreen.main.bounds.width
35
+ let size = view.bounds.size
36
+ if width == size.height, width == size.height {
37
+ return view
38
+ }
39
+ for view in view.subviews {
40
+ if let cropView = findCropView(from: view) {
41
+ return cropView
42
+ }
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func findScrollView(from view: UIView) -> UIScrollView? {
48
+ if let scrollView = view as? UIScrollView {
49
+ return scrollView
50
+ }
51
+ for view in view.subviews {
52
+ if let scrollView = findScrollView(from: view) {
53
+ return scrollView
54
+ }
55
+ }
56
+ return nil
57
+ }
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-image-picker",
3
- "version": "14.3.1",
3
+ "version": "14.4.0",
4
4
  "description": "Provides access to the system's UI for selecting images and videos from the phone's library or taking a photo with the camera.",
5
5
  "main": "build/ImagePicker.js",
6
6
  "types": "build/ImagePicker.d.ts",
@@ -44,5 +44,5 @@
44
44
  "peerDependencies": {
45
45
  "expo": "*"
46
46
  },
47
- "gitHead": "cf90d5c30c2a08a6493ebfa8aa3791aa70666759"
47
+ "gitHead": "663654577a7068c641b5e9474efbc502e3f334ea"
48
48
  }
@@ -1,2 +1,2 @@
1
- import { NativeModulesProxy } from 'expo-modules-core';
2
- export default NativeModulesProxy.ExponentImagePicker;
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ export default requireNativeModule('ExponentImagePicker');