Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

package com.facebook.react.views.image

import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.Color
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.graphics.drawable.ColorDrawable
import com.facebook.drawee.controller.ControllerListener
import com.facebook.drawee.drawable.ForwardingDrawable

// The wrapped drawable has to be one that Fresco knows how to round (a ColorDrawable here), because
// the hierarchy applies the rounding params to the leaf of the progress bar drawable.
internal open class ReactImageDownloadListener<INFO> :
ForwardingDrawable(EmptyDrawable()), ControllerListener<INFO> {
ForwardingDrawable(ColorDrawable(Color.TRANSPARENT)), ControllerListener<INFO> {
open fun onProgressChange(loaded: Int, total: Int) = Unit

override fun onLevelChange(level: Int): Boolean {
Expand All @@ -36,18 +36,6 @@ internal open class ReactImageDownloadListener<INFO> :

override fun onRelease(id: String) = Unit

/** A [Drawable] that renders nothing. */
private class EmptyDrawable : Drawable() {
override fun draw(canvas: Canvas) = Unit

override fun setAlpha(alpha: Int) = Unit

override fun setColorFilter(colorFilter: ColorFilter?) = Unit

@Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.OPAQUE"))
override fun getOpacity(): Int = PixelFormat.OPAQUE
}

companion object {
private const val MAX_LEVEL = 10000
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public class ReactImageView(
private var tilePostprocessor: TilePostprocessor? = null
private var iterativeBoxBlurPostProcessor: IterativeBoxBlurPostProcessor? = null
private var downloadListener: ReactImageDownloadListener<ImageInfo>? = null
private var progressBarListener: ReactImageDownloadListener<ImageInfo>? = null
private var controllerForTesting: ControllerListener<ImageInfo>? = null
private var fadeDurationMs = -1
private var progressiveRenderingEnabled = false
Expand Down Expand Up @@ -523,8 +524,11 @@ public class ReactImageView(
builder.setControllerListener(downloadListener)
}

if (downloadListener != null) {
// Install the progress bar once per listener: setting it again would make Fresco try to round
// the already rounded leaf drawable and log a warning on every update.
if (downloadListener != null && downloadListener !== progressBarListener) {
hierarchy.setProgressBarImage(downloadListener)
progressBarListener = downloadListener
}

controller = builder.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

package com.facebook.react.views.image

import android.content.Context
import android.graphics.Color
import android.util.DisplayMetrics
import com.facebook.common.logging.FLog
Expand All @@ -29,6 +30,9 @@ import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.events.BlackHoleEventDispatcher
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.uimanager.events.EventDispatcherProvider
import com.facebook.react.uimanager.style.BorderRadiusProp
import com.facebook.react.util.RNLog
import com.facebook.react.views.imagehelper.ImageSource
Expand All @@ -43,6 +47,7 @@ import org.mockito.MockedStatic
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
Expand Down Expand Up @@ -146,6 +151,33 @@ class ReactImagePropertyTest {
.isEqualTo(view.imageSource)
}

@Test
fun testProgressBarImageDoesNotWarnAboutRounding() {
val reactContext =
ReactContextWithEventDispatcher(RuntimeEnvironment.getApplication()).apply {
initializeWithInstance(createMockCatalystInstance())
}
val viewManager = ReactImageManager()
val themedContext = ThemedReactContext(reactContext, reactContext, null, -1)
val view = viewManager.createViewInstance(themedContext)
view.setShouldNotifyLoadEvents(true)

// Every update installs the progress bar image again unless it is already in place.
for (uri in listOf("https://example.com/a.png", "https://example.com/b.png")) {
val sources = Arguments.createArray()
val source = Arguments.createMap()
source.putString("uri", uri)
sources.pushMap(source)
viewManager.setSource(view, sources)
view.maybeUpdateView()
}

flogMock.verify(
{ FLog.w(any<String>(), eq("Don't know how to round that drawable: %s"), any<Any>()) },
never(),
)
}

@Test
fun testBorderRadius() {
val viewManager = ReactImageManager()
Expand Down Expand Up @@ -281,4 +313,10 @@ class ReactImagePropertyTest {
viewManager.setHeaders(mockView, headers)
verify(mockView).setHeaders(headers)
}

/** A [BridgeReactContext] that can also hand out an [EventDispatcher], as the real ones do. */
private class ReactContextWithEventDispatcher(context: Context) :
BridgeReactContext(context), EventDispatcherProvider {
override fun getEventDispatcher(): EventDispatcher = BlackHoleEventDispatcher
}
}
Loading