expo-image-picker 16.1.5-canary-20250613-b29d676 → 17.0.0-canary-20250701-6a945c5

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 (63) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/expo/modules/imagepicker/ExpoCropImageActivity.kt +82 -0
  5. package/android/src/main/java/expo/modules/imagepicker/ExpoCropImageUtils.kt +87 -0
  6. package/android/src/main/java/expo/modules/imagepicker/contracts/CropImageContract.kt +1 -2
  7. package/android/src/main/res/values/attrs.xml +7 -0
  8. package/android/src/main/res/values/colors.xml +10 -0
  9. package/android/src/main/res/values-night/colors.xml +10 -0
  10. package/build/ExponentImagePicker.web.d.ts.map +1 -1
  11. package/build/ExponentImagePicker.web.js +99 -58
  12. package/build/ExponentImagePicker.web.js.map +1 -1
  13. package/expo-module.config.json +1 -1
  14. package/ios/ImagePickerModule.swift +1 -1
  15. package/ios/ImagePickerOptions.swift +1 -1
  16. package/ios/ImageUtils.swift +45 -9
  17. package/ios/MediaHandler.swift +218 -44
  18. package/ios/UIImage+fixOrientation.swift +1 -3
  19. package/ios/VideoUtils.swift +6 -2
  20. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/{16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676-sources.jar → 17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5-sources.jar} +0 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5-sources.jar.md5 +1 -0
  22. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5-sources.jar.sha1 +1 -0
  23. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5-sources.jar.sha256 +1 -0
  24. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5-sources.jar.sha512 +1 -0
  25. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.aar +0 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.aar.md5 +1 -0
  27. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.aar.sha1 +1 -0
  28. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.aar.sha256 +1 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.aar.sha512 +1 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/{16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.module → 17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.module} +22 -22
  31. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.module.md5 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.module.sha1 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.module.sha256 +1 -0
  34. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.module.sha512 +1 -0
  35. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/{16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.pom → 17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.pom} +1 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.pom.md5 +1 -0
  37. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.pom.sha1 +1 -0
  38. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.pom.sha256 +1 -0
  39. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250701-6a945c5/expo.modules.imagepicker-17.0.0-canary-20250701-6a945c5.pom.sha512 +1 -0
  40. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml +4 -4
  41. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.md5 +1 -1
  42. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.sha1 +1 -1
  43. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.sha256 +1 -1
  44. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.sha512 +1 -1
  45. package/package.json +4 -4
  46. package/src/ExponentImagePicker.web.ts +104 -58
  47. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676-sources.jar.md5 +0 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676-sources.jar.sha1 +0 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676-sources.jar.sha256 +0 -1
  50. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676-sources.jar.sha512 +0 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.aar +0 -0
  52. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.aar.md5 +0 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.aar.sha1 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.aar.sha256 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.aar.sha512 +0 -1
  56. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.module.md5 +0 -1
  57. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.module.sha1 +0 -1
  58. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.module.sha256 +0 -1
  59. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.module.sha512 +0 -1
  60. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.pom.md5 +0 -1
  61. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.pom.sha1 +0 -1
  62. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.pom.sha256 +0 -1
  63. package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250613-b29d676/expo.modules.imagepicker-16.1.5-canary-20250613-b29d676.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -4,14 +4,23 @@
4
4
 
5
5
  ### 🛠 Breaking changes
6
6
 
7
+ - [Web] Changed web `uri` to use blob URLs instead of base64 data URLs for better performance. The `uri` property will now be a blob URL instead of a base64 data URL, while the `base64` property behavior remains unchanged. ([#37447](https://github.com/expo/expo/pull/37447) by [@hirbod](https://github.com/hirbod))
8
+ - [ios] The default for `preferredAssetRepresentationMode` is now `.current` instead of `.automatic`. This keeps the asset in its original container/codec (e.g. HEIC instead of JPEG) and is required for the new fast-path. Apps that relied on `.automatic` re-encoded output can pass `preferredAssetRepresentationMode: '.automatic'` to restore the old behaviour. ([#37569](https://github.com/expo/expo/pull/37569) by [@hirbod](https://github.com/hirbod))
9
+
7
10
  ### 🎉 New features
8
11
 
12
+ - [android] Introduce customizable cropping UI with light and dark theme support ([#37573](https://github.com/expo/expo/pull/37573) by [@hirbod](https://github.com/hirbod))
13
+
9
14
  ### 🐛 Bug fixes
10
15
 
11
16
  - Prevent external applications from accessing the CropImageActivity ([#37223](https://github.com/expo/expo/pull/37223) by [@aladine](https://github.com/aladine))
17
+ - [Web] Corrected camera capture attributes on web where front camera was using 'environment' and back camera was using 'user'. Reversed the values to ensure proper camera selection. ([#37447](https://github.com/expo/expo/pull/37447) by [@hirbod](https://github.com/hirbod))
12
18
 
13
19
  ### 💡 Others
14
20
 
21
+ - [ios] Images now use a _fast-path_ if possible: the original file is copied once and its size is read from the header (no full decode / re-encode). This requires `quality: 1`, `allowsEditing: false` and `preferredAssetRepresentationMode: .current` (new default) ([#37569](https://github.com/expo/expo/pull/37569) by [@hirbod](https://github.com/hirbod))
22
+ - [ios] Videos picked with `VideoExportPreset.Passthrough` are no longer transcoded and are copied only once. ([#37569](https://github.com/expo/expo/pull/37569) by [@hirbod](https://github.com/hirbod))
23
+
15
24
  ## 16.1.4 — 2025-04-30
16
25
 
17
26
  _This version does not introduce any user-facing changes._
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '16.1.5-canary-20250613-b29d676'
7
+ version = '17.0.0-canary-20250701-6a945c5'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.imagepicker"
11
11
  defaultConfig {
12
12
  versionCode 22
13
- versionName "16.1.5-canary-20250613-b29d676"
13
+ versionName "17.0.0-canary-20250701-6a945c5"
14
14
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15
15
  }
16
16
  testOptions {
@@ -22,7 +22,7 @@
22
22
  </service>
23
23
 
24
24
  <activity
25
- android:name="com.canhub.cropper.CropImageActivity"
25
+ android:name="expo.modules.imagepicker.ExpoCropImageActivity"
26
26
  android:theme="@style/Base.Theme.AppCompat"
27
27
  android:exported="false"
28
28
  tools:replace="android:exported" />
@@ -0,0 +1,82 @@
1
+ package expo.modules.imagepicker
2
+
3
+ import android.graphics.Color
4
+ import android.view.Menu
5
+ import com.canhub.cropper.CropImageActivity
6
+ import com.canhub.cropper.CropImageOptions
7
+
8
+ /**
9
+ * A wrapper around `CropImageActivity` to provide custom theming and functionality.
10
+ *
11
+ * This activity applies a custom color palette based on the application's theme
12
+ * and ensures that all icons are tinted correctly for both light and dark modes.
13
+ * It uses reflection to access private fields in the base `CropImageActivity`
14
+ * to avoid forking the library.
15
+ */
16
+ class ExpoCropImageActivity : CropImageActivity() {
17
+ private var currentIconColor: Int = Color.BLACK
18
+
19
+ // region Lifecycle Methods
20
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
21
+ super.onCreate(savedInstanceState)
22
+ // Fetch the private cropImageOptions and apply the palette as early as possible so
23
+ // the toolbar and menu icons are tinted correctly on first render.
24
+ getCropOptions()?.let { opts ->
25
+ val isNight = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES
26
+ applyPalette(isNight, opts)
27
+ invokeSetCustomizations()
28
+ invalidateOptionsMenu() // Recreate the menu to apply the new icon colors.
29
+ }
30
+ }
31
+
32
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
33
+ val result = super.onCreateOptionsMenu(menu)
34
+ tintAllMenuItems(menu)
35
+ return result
36
+ }
37
+
38
+ override fun onPrepareOptionsMenu(menu: Menu): Boolean {
39
+ val result = super.onPrepareOptionsMenu(menu)
40
+ tintAllMenuItems(menu)
41
+ return result
42
+ }
43
+ // endregion
44
+
45
+ private fun applyPalette(isNight: Boolean, opts: CropImageOptions) {
46
+ // Apply palette to options and get the toolbar widget color
47
+ val toolbarWidgetColor = ExpoCropImageUtils.applyPaletteToOptions(theme, resources, isNight, opts)
48
+
49
+ // Set the current icon color for menu tinting
50
+ currentIconColor = toolbarWidgetColor
51
+
52
+ // Set up toolbar color with fallback for status bar theming
53
+ val defaultToolbarColor = if (isNight) Color.BLACK else Color.WHITE
54
+ val toolbarColor = opts.toolbarColor ?: defaultToolbarColor
55
+ ExpoCropImageUtils.applyWindowTheming(window, toolbarColor, isNight)
56
+
57
+ // Remove action bar elevation for a flat design
58
+ supportActionBar?.elevation = 0f
59
+ }
60
+
61
+ // region Helper Methods
62
+ private fun getCropOptions(): CropImageOptions? =
63
+ runCatching {
64
+ CropImageActivity::class.java.getDeclaredField("cropImageOptions")
65
+ .apply { isAccessible = true }
66
+ .get(this) as? CropImageOptions
67
+ }.getOrNull()
68
+
69
+ private fun invokeSetCustomizations() =
70
+ runCatching {
71
+ CropImageActivity::class.java.getDeclaredMethod("setCustomizations")
72
+ .apply { isAccessible = true }
73
+ .invoke(this)
74
+ }
75
+
76
+ private fun tintAllMenuItems(menu: Menu) {
77
+ for (i in 0 until menu.size()) {
78
+ menu.getItem(i)?.icon?.mutate()?.setTint(currentIconColor)
79
+ }
80
+ }
81
+ // endregion
82
+ }
@@ -0,0 +1,87 @@
1
+ package expo.modules.imagepicker
2
+
3
+ import android.graphics.Color
4
+ import android.util.TypedValue
5
+ import com.canhub.cropper.CropImageOptions
6
+ import androidx.core.view.WindowInsetsControllerCompat
7
+
8
+ /**
9
+ * Utility functions for ExpoCropImageActivity theming and color management.
10
+ */
11
+ object ExpoCropImageUtils {
12
+
13
+ /**
14
+ * Gets a color value from the theme attributes.
15
+ * @param theme The theme to resolve the attribute from
16
+ * @param attr The attribute resource ID
17
+ * @return The color value or null if not found
18
+ */
19
+ fun getThemeColor(theme: android.content.res.Resources.Theme, attr: Int): Int? = runCatching {
20
+ val tv = TypedValue()
21
+ if (theme.resolveAttribute(attr, tv, true)) tv.data else null
22
+ }.getOrNull()
23
+
24
+ /**
25
+ * Gets a color resource value directly.
26
+ * @param resources The resources to get the color from
27
+ * @param colorResId The color resource ID
28
+ * @return The color value or null if not found
29
+ */
30
+ fun getColorResource(resources: android.content.res.Resources, colorResId: Int): Int? = runCatching {
31
+ resources.getColor(colorResId, null)
32
+ }.getOrNull()
33
+
34
+ /**
35
+ * Applies a color palette to CropImageOptions based on theme attributes or color resources.
36
+ * @param theme The theme to resolve colors from
37
+ * @param resources The resources to get colors from
38
+ * @param isNight Whether the app is in dark mode
39
+ * @param options The CropImageOptions to apply colors to
40
+ * @return The toolbar widget color that was applied
41
+ */
42
+ fun applyPaletteToOptions(
43
+ theme: android.content.res.Resources.Theme,
44
+ resources: android.content.res.Resources,
45
+ isNight: Boolean,
46
+ options: CropImageOptions
47
+ ): Int {
48
+ // Try theme attributes first, then fall back to color resources
49
+ val customToolbar = getThemeColor(theme, R.attr.expoCropToolbarColor)
50
+ ?: getColorResource(resources, R.color.expoCropToolbarColor)
51
+ val customIconColor = getThemeColor(theme, R.attr.expoCropToolbarIconColor)
52
+ ?: getColorResource(resources, R.color.expoCropToolbarIconColor)
53
+ val customActionTextColor = getThemeColor(theme, R.attr.expoCropToolbarActionTextColor)
54
+ ?: getColorResource(resources, R.color.expoCropToolbarActionTextColor)
55
+ val customBackButtonIconColor = getThemeColor(theme, R.attr.expoCropBackButtonIconColor)
56
+ ?: getColorResource(resources, R.color.expoCropBackButtonIconColor)
57
+ val customBg = getThemeColor(theme, R.attr.expoCropBackgroundColor)
58
+ ?: getColorResource(resources, R.color.expoCropBackgroundColor)
59
+
60
+ val defaultColor = if (isNight) Color.BLACK else Color.WHITE
61
+ val toolbarWidgetColor = customIconColor ?: if (isNight) Color.WHITE else Color.BLACK
62
+
63
+ options.activityBackgroundColor = customBg ?: defaultColor
64
+ options.toolbarColor = customToolbar ?: defaultColor
65
+ options.toolbarTitleColor = toolbarWidgetColor
66
+ options.toolbarBackButtonColor = customBackButtonIconColor ?: toolbarWidgetColor
67
+ options.activityMenuIconColor = toolbarWidgetColor
68
+ options.activityMenuTextColor = customActionTextColor ?: if (isNight) Color.WHITE else Color.BLACK
69
+
70
+ return toolbarWidgetColor
71
+ }
72
+
73
+ /**
74
+ * Applies window-level theming (status bar, etc.) based on the applied palette.
75
+ * @param window The window to apply theming to
76
+ * @param toolbarColor The toolbar color that was applied
77
+ * @param isNight Whether the app is in dark mode
78
+ */
79
+ fun applyWindowTheming(
80
+ window: android.view.Window,
81
+ toolbarColor: Int,
82
+ isNight: Boolean
83
+ ) {
84
+ window.statusBarColor = toolbarColor
85
+ WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars = !isNight
86
+ }
87
+ }
@@ -8,7 +8,6 @@ import androidx.core.net.toFile
8
8
  import androidx.core.net.toUri
9
9
  import androidx.core.os.bundleOf
10
10
  import com.canhub.cropper.CropImage
11
- import com.canhub.cropper.CropImageActivity
12
11
  import com.canhub.cropper.CropImageOptions
13
12
  import expo.modules.imagepicker.ImagePickerOptions
14
13
  import expo.modules.imagepicker.MediaType
@@ -24,7 +23,7 @@ import java.io.Serializable
24
23
  internal class CropImageContract(
25
24
  private val appContextProvider: AppContextProvider
26
25
  ) : AppContextActivityResultContract<CropImageContractOptions, ImagePickerContractResult> {
27
- override fun createIntent(context: Context, input: CropImageContractOptions) = Intent(context, CropImageActivity::class.java).apply {
26
+ override fun createIntent(context: Context, input: CropImageContractOptions) = Intent(context, expo.modules.imagepicker.ExpoCropImageActivity::class.java).apply {
28
27
  val mediaType = expo.modules.imagepicker.getType(context.contentResolver, input.sourceUri.toUri())
29
28
  val compressFormat = mediaType.toBitmapCompressFormat()
30
29
  val cacheDirectory = appContextProvider.appContext.cacheDirectory
@@ -0,0 +1,7 @@
1
+ <resources>
2
+ <attr name="expoCropToolbarColor" format="color"/>
3
+ <attr name="expoCropToolbarIconColor" format="color"/>
4
+ <attr name="expoCropToolbarActionTextColor" format="color"/>
5
+ <attr name="expoCropBackButtonIconColor" format="color"/>
6
+ <attr name="expoCropBackgroundColor" format="color"/>
7
+ </resources>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <!-- Custom colors for image picker cropping interface -->
4
+ <!-- Light mode defaults -->
5
+ <color name="expoCropToolbarColor">#00000000</color> <!-- Transparent -->
6
+ <color name="expoCropToolbarIconColor">#000000</color> <!-- Black icons -->
7
+ <color name="expoCropToolbarActionTextColor">#000000</color> <!-- Black text -->
8
+ <color name="expoCropBackButtonIconColor">#000000</color> <!-- Black back button -->
9
+ <color name="expoCropBackgroundColor">#ffffff</color> <!-- White background -->
10
+ </resources>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <!-- Custom colors for image picker cropping interface -->
4
+ <!-- Dark mode defaults -->
5
+ <color name="expoCropToolbarColor">#00000000</color> <!-- Transparent -->
6
+ <color name="expoCropToolbarIconColor">#ffffff</color> <!-- White icons -->
7
+ <color name="expoCropToolbarActionTextColor">#ffffff</color> <!-- White text -->
8
+ <color name="expoCropBackButtonIconColor">#ffffff</color> <!-- White back button -->
9
+ <color name="expoCropBackgroundColor">#000000</color> <!-- Black background -->
10
+ </resources>
@@ -1 +1 @@
1
- {"version":3,"file":"ExponentImagePicker.web.d.ts","sourceRoot":"","sources":["../src/ExponentImagePicker.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA8B,MAAM,mBAAmB,CAAC;AAEnF,OAAO,EAGL,kBAAkB,EAClB,iBAAiB,EAIlB,MAAM,qBAAqB,CAAC;;8EAcxB,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;oFAiB/C,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;;;gDA2BA,OAAO;oDAGH,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;;AApD7F,wBAuDE"}
1
+ {"version":3,"file":"ExponentImagePicker.web.d.ts","sourceRoot":"","sources":["../src/ExponentImagePicker.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA8B,MAAM,mBAAmB,CAAC;AAEnF,OAAO,EAGL,kBAAkB,EAClB,iBAAiB,EAGlB,MAAM,qBAAqB,CAAC;;8EAcxB,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;oFAiB/C,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;;;gDA2BA,OAAO;oDAGH,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;;AApD7F,wBAuDE"}
@@ -1,5 +1,5 @@
1
1
  import { PermissionStatus, Platform } from 'expo-modules-core';
2
- import { CameraType, MediaTypeOptions, } from './ImagePicker.types';
2
+ import { CameraType, } from './ImagePicker.types';
3
3
  import { parseMediaTypes } from './utils';
4
4
  const MediaTypeInput = {
5
5
  images: 'image/*',
@@ -18,7 +18,7 @@ export default {
18
18
  base64,
19
19
  });
20
20
  },
21
- async launchCameraAsync({ mediaTypes = MediaTypeOptions.Images, allowsMultipleSelection = false, base64 = false, cameraType, }) {
21
+ async launchCameraAsync({ mediaTypes = ['images'], allowsMultipleSelection = false, base64 = false, cameraType, }) {
22
22
  // SSR guard
23
23
  if (!Platform.isDOMAvailable) {
24
24
  return { canceled: true, assets: null };
@@ -58,6 +58,10 @@ function permissionGrantedResponse() {
58
58
  canAskAgain: true,
59
59
  };
60
60
  }
61
+ /**
62
+ * Opens a file browser dialog or camera on supported platforms and returns the selected files.
63
+ * Handles both single and multiple file selection.
64
+ */
61
65
  function openFileBrowserAsync({ mediaTypes, capture = false, allowsMultipleSelection = false, base64, }) {
62
66
  const parsedMediaTypes = parseMediaTypes(mediaTypes);
63
67
  const mediaTypeFormat = createMediaTypeFormat(parsedMediaTypes);
@@ -76,10 +80,10 @@ function openFileBrowserAsync({ mediaTypes, capture = false, allowsMultipleSelec
76
80
  input.setAttribute('capture', 'camera');
77
81
  break;
78
82
  case CameraType.front:
79
- input.setAttribute('capture', 'environment');
83
+ input.setAttribute('capture', 'user');
80
84
  break;
81
85
  case CameraType.back:
82
- input.setAttribute('capture', 'user');
86
+ input.setAttribute('capture', 'environment');
83
87
  }
84
88
  }
85
89
  document.body.appendChild(input);
@@ -102,68 +106,105 @@ function openFileBrowserAsync({ mediaTypes, capture = false, allowsMultipleSelec
102
106
  input.dispatchEvent(event);
103
107
  });
104
108
  }
105
- function readFile(targetFile, options) {
109
+ /**
110
+ * Gets metadata for an image file using a blob URL
111
+ * TODO (Hirbod): add exif support for feature parity with native
112
+ */
113
+ async function getImageMetadata(blobUrl) {
114
+ return new Promise((resolve) => {
115
+ const image = new Image();
116
+ image.onload = () => {
117
+ resolve({
118
+ width: image.naturalWidth ?? image.width,
119
+ height: image.naturalHeight ?? image.height,
120
+ });
121
+ };
122
+ image.onerror = () => resolve({ width: 0, height: 0 });
123
+ image.src = blobUrl;
124
+ });
125
+ }
126
+ /**
127
+ * Gets metadata for a video file using a blob URL
128
+ */
129
+ async function getVideoMetadata(blobUrl) {
130
+ return new Promise((resolve) => {
131
+ const video = document.createElement('video');
132
+ video.preload = 'metadata';
133
+ video.onloadedmetadata = () => {
134
+ resolve({
135
+ width: video.videoWidth,
136
+ height: video.videoHeight,
137
+ duration: video.duration,
138
+ });
139
+ };
140
+ video.onerror = () => resolve({ width: 0, height: 0, duration: 0 });
141
+ video.src = blobUrl;
142
+ });
143
+ }
144
+ /**
145
+ * Reads a file as base64
146
+ */
147
+ async function readFileAsBase64(file) {
106
148
  return new Promise((resolve, reject) => {
107
149
  const reader = new FileReader();
108
150
  reader.onerror = () => {
109
- reject(new Error(`Failed to read the selected media because the operation failed.`));
151
+ reject(new Error('Failed to read the selected media because the operation failed.'));
110
152
  };
111
- reader.onload = ({ target }) => {
112
- const uri = target.result;
113
- const returnRaw = () => resolve({ uri, width: 0, height: 0 });
114
- const returnMediaData = (data) => {
115
- resolve({
116
- ...data,
117
- ...(options.base64 && { base64: uri.substr(uri.indexOf(',') + 1) }),
118
- file: targetFile,
119
- });
120
- };
121
- if (typeof uri === 'string') {
122
- if (targetFile.type.startsWith('image/')) {
123
- const image = new Image();
124
- image.src = uri;
125
- image.onload = () => {
126
- returnMediaData({
127
- uri,
128
- width: image.naturalWidth ?? image.width,
129
- height: image.naturalHeight ?? image.height,
130
- type: 'image',
131
- mimeType: targetFile.type,
132
- fileName: targetFile.name,
133
- fileSize: targetFile.size,
134
- });
135
- };
136
- image.onerror = () => returnRaw();
137
- }
138
- else if (targetFile.type.startsWith('video/')) {
139
- const video = document.createElement('video');
140
- video.preload = 'metadata';
141
- video.src = uri;
142
- video.onloadedmetadata = () => {
143
- returnMediaData({
144
- uri,
145
- width: video.videoWidth,
146
- height: video.videoHeight,
147
- type: 'video',
148
- mimeType: targetFile.type,
149
- fileName: targetFile.name,
150
- fileSize: targetFile.size,
151
- duration: video.duration,
152
- });
153
- };
154
- video.onerror = () => returnRaw();
155
- }
156
- else {
157
- returnRaw();
158
- }
159
- }
160
- else {
161
- returnRaw();
153
+ reader.onload = (event) => {
154
+ const result = event.target?.result;
155
+ if (typeof result !== 'string') {
156
+ reject(new Error('Failed to read file as base64'));
157
+ return;
162
158
  }
159
+ // Remove the data URL prefix to get just the base64 data
160
+ resolve(result.split(',')[1]);
163
161
  };
164
- reader.readAsDataURL(targetFile);
162
+ reader.readAsDataURL(file);
165
163
  });
166
164
  }
165
+ /**
166
+ * Reads a file and returns its data as an ImagePickerAsset.
167
+ * Handles both base64 and blob URL modes, and extracts metadata for images and videos.
168
+ */
169
+ async function readFile(targetFile, options) {
170
+ const mimeType = targetFile.type;
171
+ const baseUri = URL.createObjectURL(targetFile);
172
+ try {
173
+ let metadata;
174
+ let base64;
175
+ if (mimeType.startsWith('image/')) {
176
+ metadata = await getImageMetadata(baseUri);
177
+ }
178
+ else if (mimeType.startsWith('video/')) {
179
+ metadata = await getVideoMetadata(baseUri);
180
+ }
181
+ else {
182
+ throw new Error(`Unsupported file type: ${mimeType}. Only images and videos are supported.`);
183
+ }
184
+ if (options.base64) {
185
+ base64 = await readFileAsBase64(targetFile);
186
+ }
187
+ return {
188
+ uri: baseUri,
189
+ width: metadata.width,
190
+ height: metadata.height,
191
+ type: mimeType.startsWith('image/') ? 'image' : 'video',
192
+ mimeType,
193
+ fileName: targetFile.name,
194
+ fileSize: targetFile.size,
195
+ file: targetFile,
196
+ ...(metadata.duration !== undefined && { duration: metadata.duration }),
197
+ ...(base64 && { base64 }),
198
+ };
199
+ }
200
+ catch (error) {
201
+ throw error;
202
+ }
203
+ }
204
+ /**
205
+ * Creates the accept attribute value for the file input based on the requested media types.
206
+ * Filters out livePhotos as they're not supported on web.
207
+ */
167
208
  function createMediaTypeFormat(mediaTypes) {
168
209
  const filteredMediaTypes = mediaTypes.filter((mediaType) => mediaType !== 'livePhotos');
169
210
  if (filteredMediaTypes.length === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"ExponentImagePicker.web.js","sourceRoot":"","sources":["../src/ExponentImagePicker.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { PermissionResponse, PermissionStatus, Platform } from 'expo-modules-core';\n\nimport {\n CameraType,\n ImagePickerAsset,\n ImagePickerOptions,\n ImagePickerResult,\n MediaType,\n MediaTypeOptions,\n OpenFileBrowserOptions,\n} from './ImagePicker.types';\nimport { parseMediaTypes } from './utils';\n\nconst MediaTypeInput: Record<MediaType, string> = {\n images: 'image/*',\n videos: 'video/mp4,video/quicktime,video/x-m4v,video/*',\n livePhotos: '',\n};\n\nexport default {\n async launchImageLibraryAsync({\n mediaTypes = ['images'] as MediaType[],\n allowsMultipleSelection = false,\n base64 = false,\n }: ImagePickerOptions): Promise<ImagePickerResult> {\n // SSR guard\n if (!Platform.isDOMAvailable) {\n return { canceled: true, assets: null };\n }\n return await openFileBrowserAsync({\n mediaTypes,\n allowsMultipleSelection,\n base64,\n });\n },\n\n async launchCameraAsync({\n mediaTypes = MediaTypeOptions.Images,\n allowsMultipleSelection = false,\n base64 = false,\n cameraType,\n }: ImagePickerOptions): Promise<ImagePickerResult> {\n // SSR guard\n if (!Platform.isDOMAvailable) {\n return { canceled: true, assets: null };\n }\n return await openFileBrowserAsync({\n mediaTypes,\n allowsMultipleSelection,\n capture: cameraType ?? true,\n base64,\n });\n },\n\n /*\n * Delegate to expo-permissions to request camera permissions\n */\n async getCameraPermissionsAsync() {\n return permissionGrantedResponse();\n },\n async requestCameraPermissionsAsync() {\n return permissionGrantedResponse();\n },\n\n /*\n * Camera roll permissions don't need to be requested on web, so we always\n * respond with granted.\n */\n async getMediaLibraryPermissionsAsync(_writeOnly: boolean) {\n return permissionGrantedResponse();\n },\n async requestMediaLibraryPermissionsAsync(_writeOnly: boolean): Promise<PermissionResponse> {\n return permissionGrantedResponse();\n },\n};\n\nfunction permissionGrantedResponse(): PermissionResponse {\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n granted: true,\n canAskAgain: true,\n };\n}\n\nfunction openFileBrowserAsync({\n mediaTypes,\n capture = false,\n allowsMultipleSelection = false,\n base64,\n}: OpenFileBrowserOptions): Promise<ImagePickerResult> {\n const parsedMediaTypes = parseMediaTypes(mediaTypes);\n\n const mediaTypeFormat = createMediaTypeFormat(parsedMediaTypes);\n\n const input = document.createElement('input');\n input.style.display = 'none';\n input.setAttribute('type', 'file');\n input.setAttribute('accept', mediaTypeFormat);\n input.setAttribute('id', String(Math.random()));\n input.setAttribute('data-testid', 'file-input');\n if (allowsMultipleSelection) {\n input.setAttribute('multiple', 'multiple');\n }\n if (capture) {\n switch (capture) {\n case true:\n input.setAttribute('capture', 'camera');\n break;\n case CameraType.front:\n input.setAttribute('capture', 'environment');\n break;\n case CameraType.back:\n input.setAttribute('capture', 'user');\n }\n }\n document.body.appendChild(input);\n\n return new Promise((resolve) => {\n input.addEventListener('change', async () => {\n if (input.files?.length) {\n const files = allowsMultipleSelection ? input.files : [input.files[0]];\n const assets: ImagePickerAsset[] = await Promise.all(\n Array.from(files).map((file) => readFile(file, { base64 }))\n );\n\n resolve({ canceled: false, assets });\n } else {\n resolve({ canceled: true, assets: null });\n }\n document.body.removeChild(input);\n });\n input.addEventListener('cancel', () => {\n input.dispatchEvent(new Event('change'));\n });\n\n const event = new MouseEvent('click');\n input.dispatchEvent(event);\n });\n}\n\nfunction readFile(targetFile: File, options: { base64: boolean }): Promise<ImagePickerAsset> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => {\n reject(new Error(`Failed to read the selected media because the operation failed.`));\n };\n reader.onload = ({ target }) => {\n const uri = (target as any).result;\n const returnRaw = () => resolve({ uri, width: 0, height: 0 });\n const returnMediaData = (data: ImagePickerAsset) => {\n resolve({\n ...data,\n ...(options.base64 && { base64: uri.substr(uri.indexOf(',') + 1) }),\n file: targetFile,\n });\n };\n\n if (typeof uri === 'string') {\n if (targetFile.type.startsWith('image/')) {\n const image = new Image();\n image.src = uri;\n image.onload = () => {\n returnMediaData({\n uri,\n width: image.naturalWidth ?? image.width,\n height: image.naturalHeight ?? image.height,\n type: 'image',\n mimeType: targetFile.type,\n fileName: targetFile.name,\n fileSize: targetFile.size,\n });\n };\n image.onerror = () => returnRaw();\n } else if (targetFile.type.startsWith('video/')) {\n const video = document.createElement('video');\n video.preload = 'metadata';\n video.src = uri;\n video.onloadedmetadata = () => {\n returnMediaData({\n uri,\n width: video.videoWidth,\n height: video.videoHeight,\n type: 'video',\n mimeType: targetFile.type,\n fileName: targetFile.name,\n fileSize: targetFile.size,\n duration: video.duration,\n });\n };\n video.onerror = () => returnRaw();\n } else {\n returnRaw();\n }\n } else {\n returnRaw();\n }\n };\n\n reader.readAsDataURL(targetFile);\n });\n}\n\nfunction createMediaTypeFormat(mediaTypes: MediaType[]): string {\n const filteredMediaTypes = mediaTypes.filter((mediaType) => mediaType !== 'livePhotos');\n if (filteredMediaTypes.length === 0) {\n return 'image/*';\n }\n let result = '';\n for (const mediaType of filteredMediaTypes) {\n // Make sure the types don't repeat\n if (!result.includes(MediaTypeInput[mediaType])) {\n result = result.concat(',', MediaTypeInput[mediaType]);\n }\n }\n return result;\n}\n"]}
1
+ {"version":3,"file":"ExponentImagePicker.web.js","sourceRoot":"","sources":["../src/ExponentImagePicker.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { PermissionResponse, PermissionStatus, Platform } from 'expo-modules-core';\n\nimport {\n CameraType,\n ImagePickerAsset,\n ImagePickerOptions,\n ImagePickerResult,\n MediaType,\n OpenFileBrowserOptions,\n} from './ImagePicker.types';\nimport { parseMediaTypes } from './utils';\n\nconst MediaTypeInput: Record<MediaType, string> = {\n images: 'image/*',\n videos: 'video/mp4,video/quicktime,video/x-m4v,video/*',\n livePhotos: '',\n};\n\nexport default {\n async launchImageLibraryAsync({\n mediaTypes = ['images'] as MediaType[],\n allowsMultipleSelection = false,\n base64 = false,\n }: ImagePickerOptions): Promise<ImagePickerResult> {\n // SSR guard\n if (!Platform.isDOMAvailable) {\n return { canceled: true, assets: null };\n }\n return await openFileBrowserAsync({\n mediaTypes,\n allowsMultipleSelection,\n base64,\n });\n },\n\n async launchCameraAsync({\n mediaTypes = ['images'] as MediaType[],\n allowsMultipleSelection = false,\n base64 = false,\n cameraType,\n }: ImagePickerOptions): Promise<ImagePickerResult> {\n // SSR guard\n if (!Platform.isDOMAvailable) {\n return { canceled: true, assets: null };\n }\n return await openFileBrowserAsync({\n mediaTypes,\n allowsMultipleSelection,\n capture: cameraType ?? true,\n base64,\n });\n },\n\n /*\n * Delegate to expo-permissions to request camera permissions\n */\n async getCameraPermissionsAsync() {\n return permissionGrantedResponse();\n },\n async requestCameraPermissionsAsync() {\n return permissionGrantedResponse();\n },\n\n /*\n * Camera roll permissions don't need to be requested on web, so we always\n * respond with granted.\n */\n async getMediaLibraryPermissionsAsync(_writeOnly: boolean) {\n return permissionGrantedResponse();\n },\n async requestMediaLibraryPermissionsAsync(_writeOnly: boolean): Promise<PermissionResponse> {\n return permissionGrantedResponse();\n },\n};\n\nfunction permissionGrantedResponse(): PermissionResponse {\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n granted: true,\n canAskAgain: true,\n };\n}\n\n/**\n * Opens a file browser dialog or camera on supported platforms and returns the selected files.\n * Handles both single and multiple file selection.\n */\nfunction openFileBrowserAsync({\n mediaTypes,\n capture = false,\n allowsMultipleSelection = false,\n base64,\n}: OpenFileBrowserOptions): Promise<ImagePickerResult> {\n const parsedMediaTypes = parseMediaTypes(mediaTypes);\n const mediaTypeFormat = createMediaTypeFormat(parsedMediaTypes);\n\n const input = document.createElement('input');\n input.style.display = 'none';\n input.setAttribute('type', 'file');\n input.setAttribute('accept', mediaTypeFormat);\n input.setAttribute('id', String(Math.random()));\n input.setAttribute('data-testid', 'file-input');\n if (allowsMultipleSelection) {\n input.setAttribute('multiple', 'multiple');\n }\n if (capture) {\n switch (capture) {\n case true:\n input.setAttribute('capture', 'camera');\n break;\n case CameraType.front:\n input.setAttribute('capture', 'user');\n break;\n case CameraType.back:\n input.setAttribute('capture', 'environment');\n }\n }\n document.body.appendChild(input);\n\n return new Promise((resolve) => {\n input.addEventListener('change', async () => {\n if (input.files?.length) {\n const files = allowsMultipleSelection ? input.files : [input.files[0]];\n const assets: ImagePickerAsset[] = await Promise.all(\n Array.from(files).map((file) => readFile(file, { base64 }))\n );\n\n resolve({ canceled: false, assets });\n } else {\n resolve({ canceled: true, assets: null });\n }\n document.body.removeChild(input);\n });\n input.addEventListener('cancel', () => {\n input.dispatchEvent(new Event('change'));\n });\n\n const event = new MouseEvent('click');\n input.dispatchEvent(event);\n });\n}\n\n/**\n * Gets metadata for an image file using a blob URL\n * TODO (Hirbod): add exif support for feature parity with native\n */\nasync function getImageMetadata(blobUrl: string): Promise<{ width: number; height: number }> {\n return new Promise((resolve) => {\n const image = new Image();\n image.onload = () => {\n resolve({\n width: image.naturalWidth ?? image.width,\n height: image.naturalHeight ?? image.height,\n });\n };\n image.onerror = () => resolve({ width: 0, height: 0 });\n image.src = blobUrl;\n });\n}\n\n/**\n * Gets metadata for a video file using a blob URL\n */\nasync function getVideoMetadata(\n blobUrl: string\n): Promise<{ width: number; height: number; duration: number }> {\n return new Promise((resolve) => {\n const video = document.createElement('video');\n video.preload = 'metadata';\n video.onloadedmetadata = () => {\n resolve({\n width: video.videoWidth,\n height: video.videoHeight,\n duration: video.duration,\n });\n };\n video.onerror = () => resolve({ width: 0, height: 0, duration: 0 });\n video.src = blobUrl;\n });\n}\n\n/**\n * Reads a file as base64\n */\nasync function readFileAsBase64(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => {\n reject(new Error('Failed to read the selected media because the operation failed.'));\n };\n reader.onload = (event: ProgressEvent<FileReader>) => {\n const result = event.target?.result;\n if (typeof result !== 'string') {\n reject(new Error('Failed to read file as base64'));\n return;\n }\n // Remove the data URL prefix to get just the base64 data\n resolve(result.split(',')[1]);\n };\n reader.readAsDataURL(file);\n });\n}\n\n/**\n * Reads a file and returns its data as an ImagePickerAsset.\n * Handles both base64 and blob URL modes, and extracts metadata for images and videos.\n */\nasync function readFile(targetFile: File, options: { base64: boolean }): Promise<ImagePickerAsset> {\n const mimeType = targetFile.type;\n const baseUri = URL.createObjectURL(targetFile);\n\n try {\n let metadata: { width: number; height: number; duration?: number };\n let base64: string | undefined;\n\n if (mimeType.startsWith('image/')) {\n metadata = await getImageMetadata(baseUri);\n } else if (mimeType.startsWith('video/')) {\n metadata = await getVideoMetadata(baseUri);\n } else {\n throw new Error(`Unsupported file type: ${mimeType}. Only images and videos are supported.`);\n }\n\n if (options.base64) {\n base64 = await readFileAsBase64(targetFile);\n }\n\n return {\n uri: baseUri,\n width: metadata.width,\n height: metadata.height,\n type: mimeType.startsWith('image/') ? 'image' : 'video',\n mimeType,\n fileName: targetFile.name,\n fileSize: targetFile.size,\n file: targetFile,\n ...(metadata.duration !== undefined && { duration: metadata.duration }),\n ...(base64 && { base64 }),\n };\n } catch (error) {\n throw error;\n }\n}\n\n/**\n * Creates the accept attribute value for the file input based on the requested media types.\n * Filters out livePhotos as they're not supported on web.\n */\nfunction createMediaTypeFormat(mediaTypes: MediaType[]): string {\n const filteredMediaTypes = mediaTypes.filter((mediaType) => mediaType !== 'livePhotos');\n if (filteredMediaTypes.length === 0) {\n return 'image/*';\n }\n let result = '';\n for (const mediaType of filteredMediaTypes) {\n // Make sure the types don't repeat\n if (!result.includes(MediaTypeInput[mediaType])) {\n result = result.concat(',', MediaTypeInput[mediaType]);\n }\n }\n return result;\n}\n"]}
@@ -8,7 +8,7 @@
8
8
  "publication": {
9
9
  "groupId": "host.exp.exponent",
10
10
  "artifactId": "expo.modules.imagepicker",
11
- "version": "16.1.5-canary-20250613-b29d676",
11
+ "version": "17.0.0-canary-20250701-6a945c5",
12
12
  "repository": "local-maven-repo"
13
13
  }
14
14
  }
@@ -49,7 +49,7 @@ public class ImagePickerModule: Module, OnMediaPickingResultHandler {
49
49
  self.handlePermissionRequest(requesterClass: self.getMediaLibraryPermissionRequester(writeOnly), operationType: .ask, promise: promise)
50
50
  })
51
51
 
52
- AsyncFunction("launchCameraAsync", { (options: ImagePickerOptions, promise: Promise) -> Void in
52
+ AsyncFunction("launchCameraAsync", { (options: ImagePickerOptions, promise: Promise) in
53
53
  guard let permissions = self.appContext?.permissions else {
54
54
  return promise.reject(PermissionsModuleNotFoundException())
55
55
  }
@@ -41,7 +41,7 @@ internal struct ImagePickerOptions: Record {
41
41
  var presentationStyle: PresentationStyle = .automatic
42
42
 
43
43
  @Field
44
- var preferredAssetRepresentationMode: PreferredAssetRepresentationMode = .automatic
44
+ var preferredAssetRepresentationMode: PreferredAssetRepresentationMode = .current
45
45
 
46
46
  @Field
47
47
  var cameraType: CameraType = .back