expo-image 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/image/ExpoImageModule.kt +48 -3
- package/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt +70 -56
- package/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt +27 -1
- package/android/src/main/java/expo/modules/image/Trace.kt +11 -0
- package/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt +5 -2
- package/android/src/main/java/expo/modules/image/records/SourceMap.kt +17 -4
- package/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt +29 -7
- package/build/ExpoImage.web.d.ts +1 -1
- package/build/ExpoImage.web.d.ts.map +1 -1
- package/build/ExpoImage.web.js +14 -4
- package/build/ExpoImage.web.js.map +1 -1
- package/build/Image.d.ts +10 -4
- package/build/Image.d.ts.map +1 -1
- package/build/Image.js +11 -5
- package/build/Image.js.map +1 -1
- package/ios/ImageModule.swift +20 -2
- package/ios/ImageView.swift +2 -2
- package/package.json +2 -2
- package/src/ExpoImage.web.tsx +17 -4
- package/src/Image.tsx +14 -5
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,24 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 1.8.0 — 2023-11-13
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Return a promise in the `prefetch` method. ([#25196](https://github.com/expo/expo/pull/25196) by [@gkasdorf](https://github.com/gkasdorf))
|
|
18
|
+
- [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))
|
|
19
|
+
|
|
20
|
+
### 🐛 Bug fixes
|
|
21
|
+
|
|
22
|
+
- [Android] Fix `contentFit` not working for `SVG` images. ([#25187](https://github.com/expo/expo/pull/25187) by [@behenate](https://github.com/behenate))
|
|
23
|
+
- [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))
|
|
24
|
+
- [Android] Fix `SVG` not scaling correctly in the release mode. ([#25326](https://github.com/expo/expo/pull/25326) by [@lukmccall](https://github.com/lukmccall))
|
|
25
|
+
- [Android] Fix incorrect `intrinsicSize` returned for SVGs. ([#25048](https://github.com/expo/expo/pull/25048) by [@behenate](https://github.com/behenate))
|
|
26
|
+
|
|
27
|
+
### 💡 Others
|
|
28
|
+
|
|
29
|
+
- [Android] Add tracing. ([#25251](https://github.com/expo/expo/pull/25251) by [@lukmccall](https://github.com/lukmccall))
|
|
30
|
+
|
|
13
31
|
## 1.7.0 — 2023-11-01
|
|
14
32
|
|
|
15
33
|
### 🎉 New features
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
27
|
-
val context = appContext.reactContext ?: return@
|
|
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
|
-
.
|
|
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
|
}
|
|
@@ -33,6 +33,8 @@ import expo.modules.image.records.ImageProgressEvent
|
|
|
33
33
|
import expo.modules.image.records.ImageTransition
|
|
34
34
|
import expo.modules.image.records.SourceMap
|
|
35
35
|
import expo.modules.kotlin.AppContext
|
|
36
|
+
import expo.modules.kotlin.tracing.beginAsyncTraceBlock
|
|
37
|
+
import expo.modules.kotlin.tracing.trace
|
|
36
38
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
37
39
|
import expo.modules.kotlin.views.ExpoView
|
|
38
40
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
|
@@ -263,74 +265,83 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
|
|
|
263
265
|
// However, in this case, it is safe to use as long as nothing else is added to the queue.
|
|
264
266
|
// The intention is simply to wait for the Glide code to finish before the content of the underlying views is changed during the same rendering tick.
|
|
265
267
|
mainHandler.postAtFrontOfQueue {
|
|
266
|
-
|
|
268
|
+
trace(Trace.tag, "onResourceReady") {
|
|
269
|
+
val transitionDuration = (transition?.duration ?: 0).toLong()
|
|
270
|
+
|
|
271
|
+
// If provided resource is a placeholder, but the target doesn't have a source, we treat it as a normal image.
|
|
272
|
+
if (!isPlaceholder || !target.hasSource) {
|
|
273
|
+
val (newView, previousView) = if (firstView.drawable == null) {
|
|
274
|
+
firstView to secondView
|
|
275
|
+
} else {
|
|
276
|
+
secondView to firstView
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
val clearPreviousView = {
|
|
280
|
+
previousView
|
|
281
|
+
.recycleView()
|
|
282
|
+
?.apply {
|
|
283
|
+
// When the placeholder is loaded, one target is displayed in both views.
|
|
284
|
+
// So we just have to move the reference to a new view instead of clearing the target.
|
|
285
|
+
if (this != target) {
|
|
286
|
+
clear(requestManager)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
267
290
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
291
|
+
configureView(newView, target, resource, isPlaceholder)
|
|
292
|
+
if (transitionDuration <= 0) {
|
|
293
|
+
clearPreviousView()
|
|
294
|
+
newView.alpha = 1f
|
|
295
|
+
newView.bringToFront()
|
|
296
|
+
} else {
|
|
297
|
+
newView.bringToFront()
|
|
298
|
+
previousView.alpha = 1f
|
|
299
|
+
newView.alpha = 0f
|
|
300
|
+
previousView.animate().apply {
|
|
301
|
+
duration = transitionDuration
|
|
302
|
+
alpha(0f)
|
|
303
|
+
withEndAction {
|
|
304
|
+
clearPreviousView()
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
newView.animate().apply {
|
|
308
|
+
duration = transitionDuration
|
|
309
|
+
alpha(1f)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
272
312
|
} else {
|
|
273
|
-
|
|
274
|
-
|
|
313
|
+
// We don't want to show the placeholder if something is currently displayed.
|
|
314
|
+
// There is one exception - when we're displaying a different placeholder.
|
|
315
|
+
if ((firstView.drawable != null && !firstView.isPlaceholder) || secondView.drawable != null) {
|
|
316
|
+
return@trace
|
|
317
|
+
}
|
|
275
318
|
|
|
276
|
-
|
|
277
|
-
previousView
|
|
319
|
+
firstView
|
|
278
320
|
.recycleView()
|
|
279
321
|
?.apply {
|
|
280
|
-
//
|
|
281
|
-
// So we just have to move the reference to a new view instead of clearing the target.
|
|
322
|
+
// The current target is already bound to the view. We don't want to cancel it in that case.
|
|
282
323
|
if (this != target) {
|
|
283
324
|
clear(requestManager)
|
|
284
325
|
}
|
|
285
326
|
}
|
|
286
|
-
}
|
|
287
327
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
newView.alpha = 0f
|
|
297
|
-
previousView.animate().apply {
|
|
298
|
-
duration = transitionDuration
|
|
299
|
-
alpha(0f)
|
|
300
|
-
withEndAction {
|
|
301
|
-
clearPreviousView()
|
|
328
|
+
configureView(firstView, target, resource, isPlaceholder)
|
|
329
|
+
if (transitionDuration > 0) {
|
|
330
|
+
firstView.bringToFront()
|
|
331
|
+
firstView.alpha = 0f
|
|
332
|
+
secondView.isVisible = false
|
|
333
|
+
firstView.animate().apply {
|
|
334
|
+
duration = transitionDuration
|
|
335
|
+
alpha(1f)
|
|
302
336
|
}
|
|
303
337
|
}
|
|
304
|
-
newView.animate().apply {
|
|
305
|
-
duration = transitionDuration
|
|
306
|
-
alpha(1f)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
} else {
|
|
310
|
-
// We don't want to show the placeholder if something is currently displayed.
|
|
311
|
-
// There is one exception - when we're displaying a different placeholder.
|
|
312
|
-
if ((firstView.drawable != null && !firstView.isPlaceholder) || secondView.drawable != null) {
|
|
313
|
-
return@postAtFrontOfQueue
|
|
314
338
|
}
|
|
315
339
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
clear(requestManager)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
configureView(firstView, target, resource, isPlaceholder)
|
|
326
|
-
if (transitionDuration > 0) {
|
|
327
|
-
firstView.bringToFront()
|
|
328
|
-
firstView.alpha = 0f
|
|
329
|
-
secondView.isVisible = false
|
|
330
|
-
firstView.animate().apply {
|
|
331
|
-
duration = transitionDuration
|
|
332
|
-
alpha(1f)
|
|
333
|
-
}
|
|
340
|
+
// If our image is animated, we want to see if autoplay is disabled. If it is, we should
|
|
341
|
+
// stop the animation as soon as the resource is ready. Placeholders should not follow this
|
|
342
|
+
// value since the intention is almost certainly to display the animation (i.e. a spinner)
|
|
343
|
+
if (resource is Animatable && !isPlaceholder && !autoplay) {
|
|
344
|
+
resource.stop()
|
|
334
345
|
}
|
|
335
346
|
}
|
|
336
347
|
}
|
|
@@ -428,7 +439,7 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
|
|
|
428
439
|
requestManager.clear(secondTarget)
|
|
429
440
|
}
|
|
430
441
|
|
|
431
|
-
internal fun rerenderIfNeeded(shouldRerenderBecauseOfResize: Boolean = false) {
|
|
442
|
+
internal fun rerenderIfNeeded(shouldRerenderBecauseOfResize: Boolean = false) = trace(Trace.tag, "rerenderIfNeeded(shouldRerenderBecauseOfResize=$shouldRerenderBecauseOfResize)") {
|
|
432
443
|
val bestSource = bestSource
|
|
433
444
|
val bestPlaceholder = bestPlaceholder
|
|
434
445
|
|
|
@@ -446,7 +457,7 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
|
|
|
446
457
|
loadedSource = null
|
|
447
458
|
transformationMatrixChanged = false
|
|
448
459
|
clearViewBeforeChangingSource = false
|
|
449
|
-
return
|
|
460
|
+
return@trace
|
|
450
461
|
}
|
|
451
462
|
|
|
452
463
|
val shouldRerender = sourceToLoad != loadedSource || shouldRerender || (sourceToLoad == null && placeholder != null)
|
|
@@ -579,6 +590,9 @@ class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(
|
|
|
579
590
|
.encodeQuality(100)
|
|
580
591
|
.apply(propOptions)
|
|
581
592
|
|
|
593
|
+
val cookie = Trace.getNextCookieValue()
|
|
594
|
+
beginAsyncTraceBlock(Trace.tag, Trace.loadNewImageBlock, cookie)
|
|
595
|
+
newTarget.setCookie(cookie)
|
|
582
596
|
request.into(newTarget)
|
|
583
597
|
} else {
|
|
584
598
|
// In the case where the source didn't change, but the transformation matrix has to be
|
|
@@ -18,6 +18,7 @@ import com.bumptech.glide.util.Preconditions
|
|
|
18
18
|
import com.bumptech.glide.util.Synthetic
|
|
19
19
|
import expo.modules.core.utilities.ifNull
|
|
20
20
|
import expo.modules.image.enums.ContentFit
|
|
21
|
+
import expo.modules.kotlin.tracing.endAsyncTraceBlock
|
|
21
22
|
import java.lang.ref.WeakReference
|
|
22
23
|
import kotlin.math.max
|
|
23
24
|
|
|
@@ -51,6 +52,15 @@ class ImageViewWrapperTarget(
|
|
|
51
52
|
*/
|
|
52
53
|
var sourceWidth = -1
|
|
53
54
|
|
|
55
|
+
private var cookie = -1
|
|
56
|
+
|
|
57
|
+
fun setCookie(newValue: Int) {
|
|
58
|
+
endLoadingNewImageTraceBlock()
|
|
59
|
+
synchronized(this) {
|
|
60
|
+
cookie = newValue
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
/**
|
|
55
65
|
* The content fit of the placeholder
|
|
56
66
|
*/
|
|
@@ -59,11 +69,21 @@ class ImageViewWrapperTarget(
|
|
|
59
69
|
private var request: Request? = null
|
|
60
70
|
private var sizeDeterminer = SizeDeterminer(imageViewHolder)
|
|
61
71
|
|
|
72
|
+
private fun endLoadingNewImageTraceBlock() = synchronized(this) {
|
|
73
|
+
if (cookie < 0) {
|
|
74
|
+
return@synchronized
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
endAsyncTraceBlock(Trace.tag, Trace.loadNewImageBlock, cookie)
|
|
78
|
+
cookie = -1
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
|
63
82
|
// The image view should always be valid. When the view is deallocated, all targets should be
|
|
64
83
|
// canceled. Therefore that code shouldn't be called in that case. Instead of crashing, we
|
|
65
84
|
// decided to ignore that.
|
|
66
85
|
val imageView = imageViewHolder.get().ifNull {
|
|
86
|
+
endLoadingNewImageTraceBlock()
|
|
67
87
|
Log.w("ExpoImage", "The `ExpoImageViewWrapper` was deallocated, but the target wasn't canceled in time.")
|
|
68
88
|
return
|
|
69
89
|
}
|
|
@@ -78,6 +98,10 @@ class ImageViewWrapperTarget(
|
|
|
78
98
|
false
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
if (!isPlaceholder) {
|
|
102
|
+
endLoadingNewImageTraceBlock()
|
|
103
|
+
}
|
|
104
|
+
|
|
81
105
|
imageView.onResourceReady(this, resource, isPlaceholder)
|
|
82
106
|
}
|
|
83
107
|
|
|
@@ -90,7 +114,9 @@ class ImageViewWrapperTarget(
|
|
|
90
114
|
override fun onLoadStarted(placeholder: Drawable?) = Unit
|
|
91
115
|
|
|
92
116
|
// When loading fails, it's handled by the global listener, therefore that method can be NOOP.
|
|
93
|
-
override fun onLoadFailed(errorDrawable: Drawable?)
|
|
117
|
+
override fun onLoadFailed(errorDrawable: Drawable?) {
|
|
118
|
+
endLoadingNewImageTraceBlock()
|
|
119
|
+
}
|
|
94
120
|
|
|
95
121
|
override fun onLoadCleared(placeholder: Drawable?) = Unit
|
|
96
122
|
|
|
@@ -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 =
|
|
56
|
-
height =
|
|
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
|
|
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
|
|
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
|
|
25
|
-
val
|
|
26
|
-
val
|
|
27
|
-
val
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
return SimpleResource(newDrawable)
|
|
57
|
+
return SimpleResource(SVGBitmapDrawable(context.resources, bitmap, svgIntrinsicWidth.toInt(), svgIntrinsicHeight.toInt()))
|
|
36
58
|
}
|
|
37
59
|
}
|
package/build/ExpoImage.web.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ImageNativeProps } from './Image.types';
|
|
2
2
|
export declare const ExpoImageModule: {
|
|
3
|
-
prefetch(urls: string | string[]):
|
|
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;
|
|
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"}
|
package/build/ExpoImage.web.js
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
8
|
-
* Preloaded images are
|
|
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[]):
|
|
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
|
package/build/Image.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
15
|
-
* Preloaded images are
|
|
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.
|
package/build/Image.js.map
CHANGED
|
@@ -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
|
|
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"]}
|
package/ios/ImageModule.swift
CHANGED
|
@@ -108,8 +108,26 @@ public final class ImageModule: Module {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
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
|
package/ios/ImageView.swift
CHANGED
|
@@ -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
|
|
71
|
-
if oldValue.size != bounds.size &&
|
|
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.
|
|
4
|
+
"version": "1.8.0",
|
|
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": "
|
|
36
|
+
"gitHead": "edbb34bfc68e4e129be2f65432801bd89ea17fe6"
|
|
37
37
|
}
|
package/src/ExpoImage.web.tsx
CHANGED
|
@@ -10,11 +10,24 @@ import useSourceSelection from './web/useSourceSelection';
|
|
|
10
10
|
loadStyle();
|
|
11
11
|
|
|
12
12
|
export const ExpoImageModule = {
|
|
13
|
-
prefetch(urls: string | string[]):
|
|
13
|
+
async prefetch(urls: string | string[], _): Promise<boolean> {
|
|
14
14
|
const urlsArray = Array.isArray(urls) ? urls : [urls];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
21
|
-
* Preloaded images are
|
|
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(
|
|
25
|
-
|
|
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
|
/**
|