expo-image 1.7.0 → 1.8.1

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,6 +10,28 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 1.8.1 — 2023-11-14
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Reverted ["[image][Android] Add tracing (#25251)"](https://github.com/expo/expo/pull/25251)
18
+
19
+ ## 1.8.0 — 2023-11-13
20
+
21
+ ### 🎉 New features
22
+
23
+ - Return a promise in the `prefetch` method. ([#25196](https://github.com/expo/expo/pull/25196) by [@gkasdorf](https://github.com/gkasdorf))
24
+ - [Android] Added `autoplay` prop and `startAnimating()` and `stopAnimating()` functions to reflect changes made to iOS in [#25008](https://github.com/expo/expo/pull/25008). ([#25124](https://github.com/expo/expo/pull/25124) by [@gkasdorf](https://github.com/gkasdorf))
25
+
26
+ ### 🐛 Bug fixes
27
+
28
+ - [Android] Fix `contentFit` not working for `SVG` images. ([#25187](https://github.com/expo/expo/pull/25187) by [@behenate](https://github.com/behenate))
29
+ - [iOS] Start loading the image before the view mounts to fix issues with the PagerView. ([#25343](https://github.com/expo/expo/pull/25343) by [@tsapeta](https://github.com/tsapeta))
30
+ - [Android] Fix `SVG` not scaling correctly in the release mode. ([#25326](https://github.com/expo/expo/pull/25326) by [@lukmccall](https://github.com/lukmccall))
31
+ - [Android] Fix incorrect `intrinsicSize` returned for SVGs. ([#25048](https://github.com/expo/expo/pull/25048) by [@behenate](https://github.com/behenate))
32
+
33
+ ### 💡 Others
34
+
13
35
  ## 1.7.0 — 2023-11-01
14
36
 
15
37
  ### 🎉 New features
@@ -67,7 +67,7 @@ android {
67
67
  namespace "expo.modules.image"
68
68
  defaultConfig {
69
69
  versionCode 1
70
- versionName "1.7.0"
70
+ versionName "1.8.1"
71
71
  consumerProguardFiles("proguard-rules.pro")
72
72
  }
73
73
  publishing {
@@ -1,9 +1,15 @@
1
1
  package expo.modules.image
2
2
 
3
+ import android.graphics.drawable.Drawable
3
4
  import android.view.View
4
5
  import androidx.core.view.doOnDetach
5
6
  import com.bumptech.glide.Glide
7
+ import com.bumptech.glide.load.DataSource
8
+ import com.bumptech.glide.load.engine.DiskCacheStrategy
9
+ import com.bumptech.glide.load.engine.GlideException
6
10
  import com.bumptech.glide.load.model.GlideUrl
11
+ import com.bumptech.glide.request.RequestListener
12
+ import com.bumptech.glide.request.target.Target
7
13
  import com.facebook.react.uimanager.PixelUtil
8
14
  import com.facebook.react.uimanager.Spacing
9
15
  import com.facebook.react.uimanager.ViewProps
@@ -14,6 +20,7 @@ import expo.modules.image.records.CachePolicy
14
20
  import expo.modules.image.records.ContentPosition
15
21
  import expo.modules.image.records.ImageTransition
16
22
  import expo.modules.image.records.SourceMap
23
+ import expo.modules.kotlin.Promise
17
24
  import expo.modules.kotlin.functions.Queues
18
25
  import expo.modules.kotlin.modules.Module
19
26
  import expo.modules.kotlin.modules.ModuleDefinition
@@ -23,12 +30,50 @@ class ExpoImageModule : Module() {
23
30
  override fun definition() = ModuleDefinition {
24
31
  Name("ExpoImage")
25
32
 
26
- Function("prefetch") { urls: List<String> ->
27
- val context = appContext.reactContext ?: return@Function
33
+ AsyncFunction("prefetch") { urls: List<String>, cachePolicy: CachePolicy, promise: Promise ->
34
+ val context = appContext.reactContext ?: return@AsyncFunction false
35
+
36
+ var imagesLoaded = 0
37
+ var failed = false
38
+
28
39
  urls.forEach {
29
40
  Glide
30
41
  .with(context)
31
- .download(GlideUrl(it))
42
+ .load(GlideUrl(it)) // Use `load` instead of `download` to store the asset in the memory cache
43
+ .apply {
44
+ if (cachePolicy == CachePolicy.MEMORY) {
45
+ diskCacheStrategy(DiskCacheStrategy.NONE)
46
+ }
47
+ }
48
+ .listener(object : RequestListener<Drawable> {
49
+ override fun onLoadFailed(
50
+ e: GlideException?,
51
+ model: Any?,
52
+ target: Target<Drawable>?,
53
+ isFirstResource: Boolean
54
+ ): Boolean {
55
+ if (!failed) {
56
+ failed = true
57
+ promise.resolve(false)
58
+ }
59
+ return true
60
+ }
61
+
62
+ override fun onResourceReady(
63
+ resource: Drawable?,
64
+ model: Any?,
65
+ target: Target<Drawable>?,
66
+ dataSource: DataSource?,
67
+ isFirstResource: Boolean
68
+ ): Boolean {
69
+ imagesLoaded++
70
+
71
+ if (imagesLoaded == urls.size) {
72
+ promise.resolve(true)
73
+ }
74
+ return true
75
+ }
76
+ })
32
77
  .submit()
33
78
  }
34
79
  }
@@ -11,6 +11,7 @@ import expo.modules.image.enums.ImageCacheType
11
11
  import expo.modules.image.records.ImageErrorEvent
12
12
  import expo.modules.image.records.ImageLoadEvent
13
13
  import expo.modules.image.records.ImageSource
14
+ import expo.modules.image.svg.SVGBitmapDrawable
14
15
  import java.lang.ref.WeakReference
15
16
  import java.util.*
16
17
 
@@ -47,13 +48,15 @@ class GlideRequestListener(
47
48
  dataSource: DataSource,
48
49
  isFirstResource: Boolean
49
50
  ): Boolean {
51
+ val intrinsicWidth = (resource as? SVGBitmapDrawable)?.svgIntrinsicWidth ?: resource.intrinsicWidth
52
+ val intrinsicHeight = (resource as? SVGBitmapDrawable)?.svgIntrinsicHeight ?: resource.intrinsicHeight
50
53
  expoImageViewWrapper.get()?.onLoad?.invoke(
51
54
  ImageLoadEvent(
52
55
  cacheType = ImageCacheType.fromNativeValue(dataSource).name.lowercase(Locale.getDefault()),
53
56
  source = ImageSource(
54
57
  url = model.toString(),
55
- width = resource.intrinsicWidth,
56
- height = resource.intrinsicHeight,
58
+ width = intrinsicWidth,
59
+ height = intrinsicHeight,
57
60
  mediaType = null // TODO(@lukmccall): add mediaType
58
61
  )
59
62
  )
@@ -2,6 +2,7 @@ package expo.modules.image.records
2
2
 
3
3
  import android.content.Context
4
4
  import android.net.Uri
5
+ import android.util.TypedValue
5
6
  import com.bumptech.glide.load.model.GlideUrl
6
7
  import com.bumptech.glide.load.model.Headers
7
8
  import com.bumptech.glide.load.model.LazyHeaders
@@ -38,13 +39,25 @@ data class SourceMap(
38
39
 
39
40
  private fun isLocalFileUri() = parsedUri?.scheme?.startsWith("file") ?: false
40
41
 
41
- private fun isSvg(): Boolean {
42
- var lastDotIndex = parsedUri?.toString()?.lastIndexOf('.')
42
+ private fun isSvg(context: Context): Boolean {
43
+ var uri = parsedUri?.toString()
44
+ if (uri?.startsWith("res:/") == true) {
45
+ val id = uri.removePrefix("res:/")
46
+ try {
47
+ val typedValue = TypedValue()
48
+ context.resources.getValue(id, typedValue, true)
49
+ uri = typedValue.string.toString()
50
+ } catch (e: Throwable) {
51
+ return false
52
+ }
53
+ }
54
+
55
+ var lastDotIndex = uri?.lastIndexOf('.')
43
56
  // if the path has no file extension and no . at all (e.g. file://path/to/file) return false
44
57
  if (lastDotIndex == -1 || lastDotIndex == null) {
45
58
  return false
46
59
  }
47
- return parsedUri?.toString()?.substring(lastDotIndex)?.startsWith(".svg") ?: false
60
+ return uri?.substring(lastDotIndex)?.startsWith(".svg") ?: false
48
61
  }
49
62
 
50
63
  fun isBlurhash() = parsedUri?.scheme?.startsWith("blurhash") ?: false
@@ -111,7 +124,7 @@ data class SourceMap(
111
124
 
112
125
  // Override the size for local assets (apart from SVGs). This ensures that
113
126
  // resizeMode "center" displays the image in the correct size.
114
- if (width != 0 && height != 0 && !isSvg()) {
127
+ if (width != 0 && height != 0 && !isSvg(context)) {
115
128
  override((width * scale).toInt(), (height * scale).toInt())
116
129
  }
117
130
 
@@ -1,6 +1,7 @@
1
1
  package expo.modules.image.svg
2
2
 
3
3
  import android.content.Context
4
+ import android.content.res.Resources
4
5
  import android.graphics.Bitmap
5
6
  import android.graphics.Canvas
6
7
  import android.graphics.Picture
@@ -11,8 +12,16 @@ import com.bumptech.glide.load.Options
11
12
  import com.bumptech.glide.load.engine.Resource
12
13
  import com.bumptech.glide.load.resource.SimpleResource
13
14
  import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
15
+ import com.caverock.androidsvg.PreserveAspectRatio
16
+ import com.caverock.androidsvg.RenderOptions
14
17
  import com.caverock.androidsvg.SVG
15
18
 
19
+ /**
20
+ * We have to use the intrinsicWidth/Height from the bitmap to render the image at a high enough resolution, but at the same time we want to return the actual
21
+ * preferred width and height of the SVG to JS. This class allows us to do that.
22
+ */
23
+ class SVGBitmapDrawable(res: Resources?, bitmap: Bitmap?, val svgIntrinsicWidth: Int, val svgIntrinsicHeight: Int) : BitmapDrawable(res, bitmap)
24
+
16
25
  /**
17
26
  * Convert the [SVG]'s internal representation to an Android-compatible one ([Picture]).
18
27
  *
@@ -21,17 +30,30 @@ import com.caverock.androidsvg.SVG
21
30
  */
22
31
  class SVGDrawableTranscoder(val context: Context) : ResourceTranscoder<SVG?, Drawable> {
23
32
  override fun transcode(toTranscode: Resource<SVG?>, options: Options): Resource<Drawable> {
24
- val picture = toTranscode.get().renderToPicture()
25
- val drawable = PictureDrawable(picture)
26
- val width = drawable.intrinsicWidth
27
- val height = drawable.intrinsicHeight
33
+ val svgData = toTranscode.get()
34
+ val svgIntrinsicWidth = svgData.documentViewBox.width()
35
+ val svgIntrinsicHeight = svgData.documentViewBox.height()
36
+ val documentWidth = svgData.documentWidth
37
+ val documentHeight = svgData.documentHeight
38
+ val aspectRatio = svgIntrinsicWidth / svgIntrinsicHeight
28
39
 
29
- val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
40
+ // We have no information on what content fit the user wants, so when choosing render resolution we assume
41
+ // "cover" in order to prevent loss of quality after the bitmap is transformed to appropriate `contentFit` later.
42
+ val shouldUseHeightReference = documentWidth / aspectRatio > documentHeight
43
+ val renderWidth = if (shouldUseHeightReference) documentWidth else documentHeight * aspectRatio
44
+ val renderHeight = if (shouldUseHeightReference) documentWidth / aspectRatio else documentHeight
45
+ val renderOptions = RenderOptions().apply {
46
+ viewPort(0f, 0f, renderWidth, renderHeight)
47
+ preserveAspectRatio(PreserveAspectRatio.FULLSCREEN_START)
48
+ }
49
+
50
+ val picture = svgData.renderToPicture(renderWidth.toInt(), renderHeight.toInt(), renderOptions)
51
+ val drawable = PictureDrawable(picture)
52
+ val bitmap = Bitmap.createBitmap(renderWidth.toInt(), renderHeight.toInt(), Bitmap.Config.ARGB_8888)
30
53
  val canvas = Canvas(bitmap)
31
54
  drawable.setBounds(0, 0, canvas.width, canvas.height)
32
55
  drawable.draw(canvas)
33
56
 
34
- val newDrawable = BitmapDrawable(context.resources, bitmap)
35
- return SimpleResource(newDrawable)
57
+ return SimpleResource(SVGBitmapDrawable(context.resources, bitmap, svgIntrinsicWidth.toInt(), svgIntrinsicHeight.toInt()))
36
58
  }
37
59
  }
@@ -1,6 +1,6 @@
1
1
  import { ImageNativeProps } from './Image.types';
2
2
  export declare const ExpoImageModule: {
3
- prefetch(urls: string | string[]): void;
3
+ prefetch(urls: string | string[], _: any): Promise<boolean>;
4
4
  clearMemoryCache(): Promise<boolean>;
5
5
  clearDiskCache(): Promise<boolean>;
6
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoImage.web.d.ts","sourceRoot":"","sources":["../src/ExpoImage.web.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,eAAe,CAAC;AAQlF,eAAO,MAAM,eAAe;mBACX,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;wBAQb,QAAQ,OAAO,CAAC;sBAIlB,QAAQ,OAAO,CAAC;CAGzC,CAAC;AA+BF,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,MAAM,EACN,UAAU,EACV,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,eA+ElB"}
1
+ {"version":3,"file":"ExpoImage.web.d.ts","sourceRoot":"","sources":["../src/ExpoImage.web.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,eAAe,CAAC;AAQlF,eAAO,MAAM,eAAe;mBACL,MAAM,GAAG,MAAM,EAAE,WAAM,QAAQ,OAAO,CAAC;wBAqBlC,QAAQ,OAAO,CAAC;sBAIlB,QAAQ,OAAO,CAAC;CAGzC,CAAC;AA+BF,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,MAAM,EACN,UAAU,EACV,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,eA+ElB"}
@@ -6,11 +6,21 @@ import loadStyle from './web/imageStyles';
6
6
  import useSourceSelection from './web/useSourceSelection';
7
7
  loadStyle();
8
8
  export const ExpoImageModule = {
9
- prefetch(urls) {
9
+ async prefetch(urls, _) {
10
10
  const urlsArray = Array.isArray(urls) ? urls : [urls];
11
- urlsArray.forEach((url) => {
12
- const img = new Image();
13
- img.src = url;
11
+ return new Promise((resolve) => {
12
+ let imagesLoaded = 0;
13
+ urlsArray.forEach((url) => {
14
+ const img = new Image();
15
+ img.src = url;
16
+ img.onload = () => {
17
+ imagesLoaded++;
18
+ if (imagesLoaded === urlsArray.length) {
19
+ resolve(true);
20
+ }
21
+ };
22
+ img.onerror = () => resolve(false);
23
+ });
14
24
  });
15
25
  },
16
26
  async clearMemoryCache() {
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoImage.web.js","sourceRoot":"","sources":["../src/ExpoImage.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { View } from 'react-native-web';\n\nimport { ImageNativeProps, ImageSource, ImageLoadEventData } from './Image.types';\nimport AnimationManager, { AnimationManagerNode } from './web/AnimationManager';\nimport ImageWrapper from './web/ImageWrapper';\nimport loadStyle from './web/imageStyles';\nimport useSourceSelection from './web/useSourceSelection';\n\nloadStyle();\n\nexport const ExpoImageModule = {\n prefetch(urls: string | string[]): void {\n const urlsArray = Array.isArray(urls) ? urls : [urls];\n urlsArray.forEach((url) => {\n const img = new Image();\n img.src = url;\n });\n },\n\n async clearMemoryCache(): Promise<boolean> {\n return false;\n },\n\n async clearDiskCache(): Promise<boolean> {\n return false;\n },\n};\n\nfunction onLoadAdapter(onLoad?: (event: ImageLoadEventData) => void) {\n return (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n const target = event.target as HTMLImageElement;\n onLoad?.({\n source: {\n url: target.currentSrc,\n width: target.naturalWidth,\n height: target.naturalHeight,\n mediaType: null,\n },\n cacheType: 'none',\n });\n };\n}\n\nfunction onErrorAdapter(onError?: { (event: { error: string }): void }) {\n return ({ source }: { source?: ImageSource | null }) => {\n onError?.({\n error: `Failed to load image from url: ${source?.uri}`,\n });\n };\n}\n\n// Used for some transitions to mimic native animations\nconst setCssVariables = (element: HTMLElement, size: DOMRect) => {\n element?.style.setProperty('--expo-image-width', `${size.width}px`);\n element?.style.setProperty('--expo-image-height', `${size.height}px`);\n};\n\nexport default function ExpoImage({\n source,\n placeholder,\n contentFit,\n contentPosition,\n placeholderContentFit,\n cachePolicy,\n onLoad,\n transition,\n onError,\n responsivePolicy,\n onLoadEnd,\n priority,\n blurRadius,\n recyclingKey,\n style,\n ...props\n}: ImageNativeProps) {\n const imagePlaceholderContentFit = placeholderContentFit || 'scale-down';\n const imageHashStyle = {\n objectFit: placeholderContentFit || contentFit,\n };\n const { containerRef, source: selectedSource } = useSourceSelection(\n source,\n responsivePolicy,\n setCssVariables\n );\n\n const initialNodeAnimationKey =\n (recyclingKey ? `${recyclingKey}-${placeholder?.[0]?.uri}` : placeholder?.[0]?.uri) ?? '';\n\n const initialNode: AnimationManagerNode | null = placeholder?.[0]?.uri\n ? [\n initialNodeAnimationKey,\n ({ onAnimationFinished }) =>\n (className, style) => (\n <ImageWrapper\n {...props}\n source={placeholder?.[0]}\n style={{\n objectFit: imagePlaceholderContentFit,\n ...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}),\n ...style,\n }}\n className={className}\n events={{\n onTransitionEnd: [onAnimationFinished],\n }}\n contentPosition={{ left: '50%', top: '50%' }}\n hashPlaceholderContentPosition={contentPosition}\n hashPlaceholderStyle={imageHashStyle}\n />\n ),\n ]\n : null;\n\n const currentNodeAnimationKey =\n (recyclingKey\n ? `${recyclingKey}-${selectedSource?.uri ?? placeholder?.[0]?.uri}`\n : selectedSource?.uri ?? placeholder?.[0]?.uri) ?? '';\n\n const currentNode: AnimationManagerNode = [\n currentNodeAnimationKey,\n ({ onAnimationFinished, onReady, onMount, onError: onErrorInner }) =>\n (className, style) => (\n <ImageWrapper\n {...props}\n source={selectedSource || placeholder?.[0]}\n events={{\n onError: [onErrorAdapter(onError), onLoadEnd, onErrorInner],\n onLoad: [onLoadAdapter(onLoad), onLoadEnd, onReady],\n onMount: [onMount],\n onTransitionEnd: [onAnimationFinished],\n }}\n style={{\n objectFit: selectedSource ? contentFit : imagePlaceholderContentFit,\n ...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}),\n ...style,\n }}\n className={className}\n cachePolicy={cachePolicy}\n priority={priority}\n contentPosition={selectedSource ? contentPosition : { top: '50%', left: '50%' }}\n hashPlaceholderContentPosition={contentPosition}\n hashPlaceholderStyle={imageHashStyle}\n accessibilityLabel={props.accessibilityLabel}\n />\n ),\n ];\n return (\n <View ref={containerRef} dataSet={{ expoimage: true }} style={[{ overflow: 'hidden' }, style]}>\n <AnimationManager transition={transition} recyclingKey={recyclingKey} initial={initialNode}>\n {currentNode}\n </AnimationManager>\n </View>\n );\n}\n"]}
1
+ {"version":3,"file":"ExpoImage.web.js","sourceRoot":"","sources":["../src/ExpoImage.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { View } from 'react-native-web';\n\nimport { ImageNativeProps, ImageSource, ImageLoadEventData } from './Image.types';\nimport AnimationManager, { AnimationManagerNode } from './web/AnimationManager';\nimport ImageWrapper from './web/ImageWrapper';\nimport loadStyle from './web/imageStyles';\nimport useSourceSelection from './web/useSourceSelection';\n\nloadStyle();\n\nexport const ExpoImageModule = {\n async prefetch(urls: string | string[], _): Promise<boolean> {\n const urlsArray = Array.isArray(urls) ? urls : [urls];\n\n return new Promise<boolean>((resolve) => {\n let imagesLoaded = 0;\n\n urlsArray.forEach((url) => {\n const img = new Image();\n img.src = url;\n img.onload = () => {\n imagesLoaded++;\n\n if (imagesLoaded === urlsArray.length) {\n resolve(true);\n }\n };\n img.onerror = () => resolve(false);\n });\n });\n },\n\n async clearMemoryCache(): Promise<boolean> {\n return false;\n },\n\n async clearDiskCache(): Promise<boolean> {\n return false;\n },\n};\n\nfunction onLoadAdapter(onLoad?: (event: ImageLoadEventData) => void) {\n return (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n const target = event.target as HTMLImageElement;\n onLoad?.({\n source: {\n url: target.currentSrc,\n width: target.naturalWidth,\n height: target.naturalHeight,\n mediaType: null,\n },\n cacheType: 'none',\n });\n };\n}\n\nfunction onErrorAdapter(onError?: { (event: { error: string }): void }) {\n return ({ source }: { source?: ImageSource | null }) => {\n onError?.({\n error: `Failed to load image from url: ${source?.uri}`,\n });\n };\n}\n\n// Used for some transitions to mimic native animations\nconst setCssVariables = (element: HTMLElement, size: DOMRect) => {\n element?.style.setProperty('--expo-image-width', `${size.width}px`);\n element?.style.setProperty('--expo-image-height', `${size.height}px`);\n};\n\nexport default function ExpoImage({\n source,\n placeholder,\n contentFit,\n contentPosition,\n placeholderContentFit,\n cachePolicy,\n onLoad,\n transition,\n onError,\n responsivePolicy,\n onLoadEnd,\n priority,\n blurRadius,\n recyclingKey,\n style,\n ...props\n}: ImageNativeProps) {\n const imagePlaceholderContentFit = placeholderContentFit || 'scale-down';\n const imageHashStyle = {\n objectFit: placeholderContentFit || contentFit,\n };\n const { containerRef, source: selectedSource } = useSourceSelection(\n source,\n responsivePolicy,\n setCssVariables\n );\n\n const initialNodeAnimationKey =\n (recyclingKey ? `${recyclingKey}-${placeholder?.[0]?.uri}` : placeholder?.[0]?.uri) ?? '';\n\n const initialNode: AnimationManagerNode | null = placeholder?.[0]?.uri\n ? [\n initialNodeAnimationKey,\n ({ onAnimationFinished }) =>\n (className, style) => (\n <ImageWrapper\n {...props}\n source={placeholder?.[0]}\n style={{\n objectFit: imagePlaceholderContentFit,\n ...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}),\n ...style,\n }}\n className={className}\n events={{\n onTransitionEnd: [onAnimationFinished],\n }}\n contentPosition={{ left: '50%', top: '50%' }}\n hashPlaceholderContentPosition={contentPosition}\n hashPlaceholderStyle={imageHashStyle}\n />\n ),\n ]\n : null;\n\n const currentNodeAnimationKey =\n (recyclingKey\n ? `${recyclingKey}-${selectedSource?.uri ?? placeholder?.[0]?.uri}`\n : selectedSource?.uri ?? placeholder?.[0]?.uri) ?? '';\n\n const currentNode: AnimationManagerNode = [\n currentNodeAnimationKey,\n ({ onAnimationFinished, onReady, onMount, onError: onErrorInner }) =>\n (className, style) => (\n <ImageWrapper\n {...props}\n source={selectedSource || placeholder?.[0]}\n events={{\n onError: [onErrorAdapter(onError), onLoadEnd, onErrorInner],\n onLoad: [onLoadAdapter(onLoad), onLoadEnd, onReady],\n onMount: [onMount],\n onTransitionEnd: [onAnimationFinished],\n }}\n style={{\n objectFit: selectedSource ? contentFit : imagePlaceholderContentFit,\n ...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}),\n ...style,\n }}\n className={className}\n cachePolicy={cachePolicy}\n priority={priority}\n contentPosition={selectedSource ? contentPosition : { top: '50%', left: '50%' }}\n hashPlaceholderContentPosition={contentPosition}\n hashPlaceholderStyle={imageHashStyle}\n accessibilityLabel={props.accessibilityLabel}\n />\n ),\n ];\n return (\n <View ref={containerRef} dataSet={{ expoimage: true }} style={[{ overflow: 'hidden' }, style]}>\n <AnimationManager transition={transition} recyclingKey={recyclingKey} initial={initialNode}>\n {currentNode}\n </AnimationManager>\n </View>\n );\n}\n"]}
package/build/Image.d.ts CHANGED
@@ -4,11 +4,17 @@ export declare class Image extends React.PureComponent<ImageProps> {
4
4
  nativeViewRef: any;
5
5
  constructor(props: any);
6
6
  /**
7
- * Preloads images at the given urls that can be later used in the image view.
8
- * Preloaded images are always cached on the disk, so make sure to use
9
- * `disk` (default) or `memory-disk` cache policy.
7
+ * Preloads images at the given URLs that can be later used in the image view.
8
+ * Preloaded images are cached to the memory and disk by default, so make sure
9
+ * to use `disk` (default) or `memory-disk` [cache policy](#cachepolicy).
10
+ * @param urls - A URL string or an array of URLs of images to prefetch.
11
+ * @param cachePolicy - The cache policy for prefetched images.
12
+ * @return A promise resolving to `true` as soon as all images have been
13
+ * successfully prefetched. If an image fails to be prefetched, the promise
14
+ * will immediately resolve to `false` regardless of whether other images have
15
+ * finished prefetching.
10
16
  */
11
- static prefetch(urls: string | string[]): void;
17
+ static prefetch(urls: string | string[], cachePolicy?: 'memory-disk' | 'memory'): Promise<boolean>;
12
18
  /**
13
19
  * Asynchronously clears all images stored in memory.
14
20
  * @platform android
@@ -1 +1 @@
1
- {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../src/Image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAM3C,qBAAa,KAAM,SAAQ,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC;IACxD,aAAa,MAAC;gBAEF,KAAK,KAAA;IAKjB;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;IAI9C;;;;;;;OAOG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjD;;;;;;;OAOG;WACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/C;;;;;;;;;OASG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIxE;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,MAAM;CAqCP"}
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../src/Image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAM3C,qBAAa,KAAM,SAAQ,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC;IACxD,aAAa,MAAC;gBAEF,KAAK,KAAA;IAKjB;;;;;;;;;;OAUG;WACU,QAAQ,CACnB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,WAAW,GAAE,aAAa,GAAG,QAAwB,GACpD,OAAO,CAAC,OAAO,CAAC;IAInB;;;;;;;OAOG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjD;;;;;;;OAOG;WACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/C;;;;;;;;;OASG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIxE;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,MAAM;CAqCP"}
package/build/Image.js CHANGED
@@ -11,12 +11,18 @@ export class Image extends React.PureComponent {
11
11
  this.nativeViewRef = React.createRef();
12
12
  }
13
13
  /**
14
- * Preloads images at the given urls that can be later used in the image view.
15
- * Preloaded images are always cached on the disk, so make sure to use
16
- * `disk` (default) or `memory-disk` cache policy.
14
+ * Preloads images at the given URLs that can be later used in the image view.
15
+ * Preloaded images are cached to the memory and disk by default, so make sure
16
+ * to use `disk` (default) or `memory-disk` [cache policy](#cachepolicy).
17
+ * @param urls - A URL string or an array of URLs of images to prefetch.
18
+ * @param cachePolicy - The cache policy for prefetched images.
19
+ * @return A promise resolving to `true` as soon as all images have been
20
+ * successfully prefetched. If an image fails to be prefetched, the promise
21
+ * will immediately resolve to `false` regardless of whether other images have
22
+ * finished prefetching.
17
23
  */
18
- static prefetch(urls) {
19
- return ExpoImageModule.prefetch(Array.isArray(urls) ? urls : [urls]);
24
+ static async prefetch(urls, cachePolicy = 'memory-disk') {
25
+ return ExpoImageModule.prefetch(Array.isArray(urls) ? urls : [urls], cachePolicy);
20
26
  }
21
27
  /**
22
28
  * Asynchronously clears all images stored in memory.
@@ -1 +1 @@
1
- {"version":3,"file":"Image.js","sourceRoot":"","sources":["../src/Image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,SAAS,EAAE,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,IAAI,qCAAqC,GAAG,KAAK,CAAC;AAElD,MAAM,OAAO,KAAM,SAAQ,KAAK,CAAC,aAAyB;IACxD,aAAa,CAAC;IAEd,YAAY,KAAK;QACf,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAuB;QACrC,OAAO,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,gBAAgB;QAC3B,OAAO,MAAM,eAAe,CAAC,gBAAgB,EAAE,CAAC;IAClD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,cAAc;QACzB,OAAO,MAAM,eAAe,CAAC,cAAc,EAAE,CAAC;IAChD,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,QAAgB;QAC7C,OAAO,MAAM,eAAe,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc;QAClB,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;IACpD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;IACnD,CAAC;IAED,MAAM;QACJ,MAAM,EACJ,KAAK,EACL,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,UAAU,EACV,YAAY,EACZ,UAAU,EAAE,cAAc,EAC1B,aAAa,EACb,sBAAsB,EACtB,GAAG,SAAS,EACb,GAAG,IAAI,CAAC,KAAK,CAAC;QAEf,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,SAAS,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACtF,MAAM,UAAU,GAAG,cAAc,IAAI,eAAe,CAAC;QAErD,IAAI,CAAC,aAAa,IAAI,sBAAsB,CAAC,IAAI,CAAC,qCAAqC,EAAE;YACvF,OAAO,CAAC,IAAI,CACV,4GAA4G,CAC7G,CAAC;YACF,qCAAqC,GAAG,IAAI,CAAC;SAC9C;QAED,OAAO,CACL,CAAC,SAAS,CACR,IAAI,SAAS,CAAC,CACd,KAAK,CAAC,CAAC,SAAS,CAAC,CACjB,MAAM,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAC/B,WAAW,CAAC,CAAC,cAAc,CAAC,WAAW,IAAI,aAAa,IAAI,sBAAsB,CAAC,CAAC,CACpF,UAAU,CAAC,CAAC,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CACtD,eAAe,CAAC,CAAC,sBAAsB,CAAC,eAAe,CAAC,CAAC,CACzD,UAAU,CAAC,CAAC,iBAAiB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,EACxD,CACH,CAAC;IACJ,CAAC;CACF","sourcesContent":["import React from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport ExpoImage, { ExpoImageModule } from './ExpoImage';\nimport { ImageProps } from './Image.types';\nimport { resolveContentFit, resolveContentPosition, resolveTransition } from './utils';\nimport { resolveSources } from './utils/resolveSources';\n\nlet loggedDefaultSourceDeprecationWarning = false;\n\nexport class Image extends React.PureComponent<ImageProps> {\n nativeViewRef;\n\n constructor(props) {\n super(props);\n this.nativeViewRef = React.createRef();\n }\n\n /**\n * Preloads images at the given urls that can be later used in the image view.\n * Preloaded images are always cached on the disk, so make sure to use\n * `disk` (default) or `memory-disk` cache policy.\n */\n static prefetch(urls: string | string[]): void {\n return ExpoImageModule.prefetch(Array.isArray(urls) ? urls : [urls]);\n }\n\n /**\n * Asynchronously clears all images stored in memory.\n * @platform android\n * @platform ios\n * @return A promise resolving to `true` when the operation succeeds.\n * It may resolve to `false` on Android when the activity is no longer available.\n * Resolves to `false` on Web.\n */\n static async clearMemoryCache(): Promise<boolean> {\n return await ExpoImageModule.clearMemoryCache();\n }\n\n /**\n * Asynchronously clears all images from the disk cache.\n * @platform android\n * @platform ios\n * @return A promise resolving to `true` when the operation succeeds.\n * It may resolve to `false` on Android when the activity is no longer available.\n * Resolves to `false` on Web.\n */\n static async clearDiskCache(): Promise<boolean> {\n return await ExpoImageModule.clearDiskCache();\n }\n\n /**\n * Asynchronously checks if an image exists in the disk cache and resolves to\n * the path of the cached image if it does.\n * @param cacheKey - The cache key for the requested image. Unless you have set\n * a custom cache key, this will be the source URL of the image.\n * @platform android\n * @platform ios\n * @return A promise resolving to the path of the cached image. It will resolve\n * to `null` if the image does not exist in the cache.\n */\n static async getCachePathAsync(cacheKey: string): Promise<string | null> {\n return await ExpoImageModule.getCachePathAsync(cacheKey);\n }\n\n /**\n * Asynchronously starts playback of the view's image if it is animated.\n * @platform ios\n */\n async startAnimating(): Promise<void> {\n await this.nativeViewRef.current.startAnimating();\n }\n\n /**\n * Asynchronously stops the playback of the view's image if it is animated.\n * @platform ios\n */\n async stopAnimating(): Promise<void> {\n await this.nativeViewRef.current.stopAnimating();\n }\n\n render() {\n const {\n style,\n source,\n placeholder,\n contentFit,\n contentPosition,\n transition,\n fadeDuration,\n resizeMode: resizeModeProp,\n defaultSource,\n loadingIndicatorSource,\n ...restProps\n } = this.props;\n\n const { resizeMode: resizeModeStyle, ...restStyle } = StyleSheet.flatten(style) || {};\n const resizeMode = resizeModeProp ?? resizeModeStyle;\n\n if ((defaultSource || loadingIndicatorSource) && !loggedDefaultSourceDeprecationWarning) {\n console.warn(\n '[expo-image]: `defaultSource` and `loadingIndicatorSource` props are deprecated, use `placeholder` instead'\n );\n loggedDefaultSourceDeprecationWarning = true;\n }\n\n return (\n <ExpoImage\n {...restProps}\n style={restStyle}\n source={resolveSources(source)}\n placeholder={resolveSources(placeholder ?? defaultSource ?? loadingIndicatorSource)}\n contentFit={resolveContentFit(contentFit, resizeMode)}\n contentPosition={resolveContentPosition(contentPosition)}\n transition={resolveTransition(transition, fadeDuration)}\n />\n );\n }\n}\n"]}
1
+ {"version":3,"file":"Image.js","sourceRoot":"","sources":["../src/Image.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport ExpoImage, { ExpoImageModule } from './ExpoImage';\nimport { ImageProps } from './Image.types';\nimport { resolveContentFit, resolveContentPosition, resolveTransition } from './utils';\nimport { resolveSources } from './utils/resolveSources';\n\nlet loggedDefaultSourceDeprecationWarning = false;\n\nexport class Image extends React.PureComponent<ImageProps> {\n nativeViewRef;\n\n constructor(props) {\n super(props);\n this.nativeViewRef = React.createRef();\n }\n\n /**\n * Preloads images at the given URLs that can be later used in the image view.\n * Preloaded images are cached to the memory and disk by default, so make sure\n * to use `disk` (default) or `memory-disk` [cache policy](#cachepolicy).\n * @param urls - A URL string or an array of URLs of images to prefetch.\n * @param cachePolicy - The cache policy for prefetched images.\n * @return A promise resolving to `true` as soon as all images have been\n * successfully prefetched. If an image fails to be prefetched, the promise\n * will immediately resolve to `false` regardless of whether other images have\n * finished prefetching.\n */\n static async prefetch(\n urls: string | string[],\n cachePolicy: 'memory-disk' | 'memory' = 'memory-disk'\n ): Promise<boolean> {\n return ExpoImageModule.prefetch(Array.isArray(urls) ? urls : [urls], cachePolicy);\n }\n\n /**\n * Asynchronously clears all images stored in memory.\n * @platform android\n * @platform ios\n * @return A promise resolving to `true` when the operation succeeds.\n * It may resolve to `false` on Android when the activity is no longer available.\n * Resolves to `false` on Web.\n */\n static async clearMemoryCache(): Promise<boolean> {\n return await ExpoImageModule.clearMemoryCache();\n }\n\n /**\n * Asynchronously clears all images from the disk cache.\n * @platform android\n * @platform ios\n * @return A promise resolving to `true` when the operation succeeds.\n * It may resolve to `false` on Android when the activity is no longer available.\n * Resolves to `false` on Web.\n */\n static async clearDiskCache(): Promise<boolean> {\n return await ExpoImageModule.clearDiskCache();\n }\n\n /**\n * Asynchronously checks if an image exists in the disk cache and resolves to\n * the path of the cached image if it does.\n * @param cacheKey - The cache key for the requested image. Unless you have set\n * a custom cache key, this will be the source URL of the image.\n * @platform android\n * @platform ios\n * @return A promise resolving to the path of the cached image. It will resolve\n * to `null` if the image does not exist in the cache.\n */\n static async getCachePathAsync(cacheKey: string): Promise<string | null> {\n return await ExpoImageModule.getCachePathAsync(cacheKey);\n }\n\n /**\n * Asynchronously starts playback of the view's image if it is animated.\n * @platform ios\n */\n async startAnimating(): Promise<void> {\n await this.nativeViewRef.current.startAnimating();\n }\n\n /**\n * Asynchronously stops the playback of the view's image if it is animated.\n * @platform ios\n */\n async stopAnimating(): Promise<void> {\n await this.nativeViewRef.current.stopAnimating();\n }\n\n render() {\n const {\n style,\n source,\n placeholder,\n contentFit,\n contentPosition,\n transition,\n fadeDuration,\n resizeMode: resizeModeProp,\n defaultSource,\n loadingIndicatorSource,\n ...restProps\n } = this.props;\n\n const { resizeMode: resizeModeStyle, ...restStyle } = StyleSheet.flatten(style) || {};\n const resizeMode = resizeModeProp ?? resizeModeStyle;\n\n if ((defaultSource || loadingIndicatorSource) && !loggedDefaultSourceDeprecationWarning) {\n console.warn(\n '[expo-image]: `defaultSource` and `loadingIndicatorSource` props are deprecated, use `placeholder` instead'\n );\n loggedDefaultSourceDeprecationWarning = true;\n }\n\n return (\n <ExpoImage\n {...restProps}\n style={restStyle}\n source={resolveSources(source)}\n placeholder={resolveSources(placeholder ?? defaultSource ?? loadingIndicatorSource)}\n contentFit={resolveContentFit(contentFit, resizeMode)}\n contentPosition={resolveContentPosition(contentPosition)}\n transition={resolveTransition(transition, fadeDuration)}\n />\n );\n }\n}\n"]}
@@ -108,8 +108,26 @@ public final class ImageModule: Module {
108
108
  }
109
109
  }
110
110
 
111
- Function("prefetch") { (urls: [URL]) in
112
- SDWebImagePrefetcher.shared.prefetchURLs(urls)
111
+ AsyncFunction("prefetch") { (urls: [URL], cachePolicy: ImageCachePolicy, promise: Promise) in
112
+ var context = SDWebImageContext()
113
+ context[.storeCacheType] = cachePolicy.toSdCacheType().rawValue
114
+
115
+ var imagesLoaded = 0
116
+ var failed = false
117
+
118
+ urls.forEach { url in
119
+ SDWebImagePrefetcher.shared.prefetchURLs([url], context: context, progress: nil, completed: { loaded, skipped in
120
+ if skipped > 0 && !failed {
121
+ failed = true
122
+ promise.resolve(false)
123
+ } else {
124
+ imagesLoaded = imagesLoaded + 1
125
+ if imagesLoaded == urls.count {
126
+ promise.resolve(true)
127
+ }
128
+ }
129
+ })
130
+ }
113
131
  }
114
132
 
115
133
  AsyncFunction("clearMemoryCache") { () -> Bool in
@@ -67,8 +67,8 @@ public final class ImageView: ExpoView {
67
67
 
68
68
  public override var bounds: CGRect {
69
69
  didSet {
70
- // Reload the image when the bounds size has changed and the view is mounted.
71
- if oldValue.size != bounds.size && window != nil {
70
+ // Reload the image when the bounds size has changed and is not empty.
71
+ if oldValue.size != bounds.size && bounds.size != .zero {
72
72
  reload()
73
73
  }
74
74
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-image",
3
3
  "title": "Expo Image",
4
- "version": "1.7.0",
4
+ "version": "1.8.1",
5
5
  "description": "A cross-platform, performant image component for React Native and Expo with Web support",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
@@ -33,5 +33,5 @@
33
33
  "peerDependencies": {
34
34
  "expo": "*"
35
35
  },
36
- "gitHead": "8ff6964633de470960eb27390d67fcf93e58638d"
36
+ "gitHead": "8951e9a6edc691f350c8e894863b6cf14720317a"
37
37
  }
@@ -10,11 +10,24 @@ import useSourceSelection from './web/useSourceSelection';
10
10
  loadStyle();
11
11
 
12
12
  export const ExpoImageModule = {
13
- prefetch(urls: string | string[]): void {
13
+ async prefetch(urls: string | string[], _): Promise<boolean> {
14
14
  const urlsArray = Array.isArray(urls) ? urls : [urls];
15
- urlsArray.forEach((url) => {
16
- const img = new Image();
17
- img.src = url;
15
+
16
+ return new Promise<boolean>((resolve) => {
17
+ let imagesLoaded = 0;
18
+
19
+ urlsArray.forEach((url) => {
20
+ const img = new Image();
21
+ img.src = url;
22
+ img.onload = () => {
23
+ imagesLoaded++;
24
+
25
+ if (imagesLoaded === urlsArray.length) {
26
+ resolve(true);
27
+ }
28
+ };
29
+ img.onerror = () => resolve(false);
30
+ });
18
31
  });
19
32
  },
20
33
 
package/src/Image.tsx CHANGED
@@ -17,12 +17,21 @@ export class Image extends React.PureComponent<ImageProps> {
17
17
  }
18
18
 
19
19
  /**
20
- * Preloads images at the given urls that can be later used in the image view.
21
- * Preloaded images are always cached on the disk, so make sure to use
22
- * `disk` (default) or `memory-disk` cache policy.
20
+ * Preloads images at the given URLs that can be later used in the image view.
21
+ * Preloaded images are cached to the memory and disk by default, so make sure
22
+ * to use `disk` (default) or `memory-disk` [cache policy](#cachepolicy).
23
+ * @param urls - A URL string or an array of URLs of images to prefetch.
24
+ * @param cachePolicy - The cache policy for prefetched images.
25
+ * @return A promise resolving to `true` as soon as all images have been
26
+ * successfully prefetched. If an image fails to be prefetched, the promise
27
+ * will immediately resolve to `false` regardless of whether other images have
28
+ * finished prefetching.
23
29
  */
24
- static prefetch(urls: string | string[]): void {
25
- return ExpoImageModule.prefetch(Array.isArray(urls) ? urls : [urls]);
30
+ static async prefetch(
31
+ urls: string | string[],
32
+ cachePolicy: 'memory-disk' | 'memory' = 'memory-disk'
33
+ ): Promise<boolean> {
34
+ return ExpoImageModule.prefetch(Array.isArray(urls) ? urls : [urls], cachePolicy);
26
35
  }
27
36
 
28
37
  /**