expo-image-picker 16.1.5-canary-20250612-338ef55 → 17.0.0-canary-20250630-547cd82
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 +9 -0
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/expo/modules/imagepicker/ExpoCropImageActivity.kt +82 -0
- package/android/src/main/java/expo/modules/imagepicker/ExpoCropImageUtils.kt +87 -0
- package/android/src/main/java/expo/modules/imagepicker/contracts/CropImageContract.kt +1 -2
- package/android/src/main/res/values/attrs.xml +7 -0
- package/android/src/main/res/values/colors.xml +10 -0
- package/android/src/main/res/values-night/colors.xml +10 -0
- package/build/ExponentImagePicker.web.d.ts.map +1 -1
- package/build/ExponentImagePicker.web.js +99 -58
- package/build/ExponentImagePicker.web.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/ImagePickerModule.swift +1 -1
- package/ios/ImagePickerOptions.swift +1 -1
- package/ios/ImageUtils.swift +45 -9
- package/ios/MediaHandler.swift +218 -44
- package/ios/UIImage+fixOrientation.swift +1 -3
- package/ios/VideoUtils.swift +6 -2
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/{16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55-sources.jar → 17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82-sources.jar} +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/{16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.module → 17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.module} +22 -22
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/{16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.pom → 17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.pom} +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/17.0.0-canary-20250630-547cd82/expo.modules.imagepicker-17.0.0-canary-20250630-547cd82.pom.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml +4 -4
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -4
- package/src/ExponentImagePicker.web.ts +104 -58
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.imagepicker/16.1.5-canary-20250612-338ef55/expo.modules.imagepicker-16.1.5-canary-20250612-338ef55.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._
|
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '
|
|
7
|
+
version = '17.0.0-canary-20250630-547cd82'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.imagepicker"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 22
|
|
13
|
-
versionName "
|
|
13
|
+
versionName "17.0.0-canary-20250630-547cd82"
|
|
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="
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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', '
|
|
83
|
+
input.setAttribute('capture', 'user');
|
|
80
84
|
break;
|
|
81
85
|
case CameraType.back:
|
|
82
|
-
input.setAttribute('capture', '
|
|
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
|
-
|
|
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(
|
|
151
|
+
reject(new Error('Failed to read the selected media because the operation failed.'));
|
|
110
152
|
};
|
|
111
|
-
reader.onload = (
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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(
|
|
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"]}
|
package/expo-module.config.json
CHANGED
|
@@ -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)
|
|
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 = .
|
|
44
|
+
var preferredAssetRepresentationMode: PreferredAssetRepresentationMode = .current
|
|
45
45
|
|
|
46
46
|
@Field
|
|
47
47
|
var cameraType: CameraType = .back
|