diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8afeee79e05f..18c83972d50f 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6345,51 +6345,57 @@ public final class com/facebook/react/views/image/ImageResizeMode { public static final fun toTileMode (Ljava/lang/String;)Landroid/graphics/Shader$TileMode; } -public class com/facebook/react/views/image/MultiPostprocessor : com/facebook/imagepipeline/request/Postprocessor { - public static fun from (Ljava/util/List;)Lcom/facebook/imagepipeline/request/Postprocessor; +public final class com/facebook/react/views/image/MultiPostprocessor : com/facebook/imagepipeline/request/Postprocessor { + public static final field Companion Lcom/facebook/react/views/image/MultiPostprocessor$Companion; + public synthetic fun (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun from (Ljava/util/List;)Lcom/facebook/imagepipeline/request/Postprocessor; public fun getName ()Ljava/lang/String; public fun getPostprocessorCacheKey ()Lcom/facebook/cache/common/CacheKey; public fun process (Landroid/graphics/Bitmap;Lcom/facebook/imagepipeline/bitmaps/PlatformBitmapFactory;)Lcom/facebook/common/references/CloseableReference; } +public final class com/facebook/react/views/image/MultiPostprocessor$Companion { + public final fun from (Ljava/util/List;)Lcom/facebook/imagepipeline/request/Postprocessor; +} + public abstract interface class com/facebook/react/views/image/ReactCallerContextFactory { public abstract fun getOrCreateCallerContext (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; } -public class com/facebook/react/views/image/ReactImageManager : com/facebook/react/uimanager/SimpleViewManager { +public final class com/facebook/react/views/image/ReactImageManager : com/facebook/react/uimanager/SimpleViewManager { + public static final field Companion Lcom/facebook/react/views/image/ReactImageManager$Companion; public static final field REACT_CLASS Ljava/lang/String; public fun ()V + public fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;)V + public fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Lcom/facebook/react/views/image/GlobalImageLoadListener;)V public fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Lcom/facebook/react/views/image/GlobalImageLoadListener;Lcom/facebook/react/views/image/ReactCallerContextFactory;)V + public synthetic fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Lcom/facebook/react/views/image/GlobalImageLoadListener;Lcom/facebook/react/views/image/ReactCallerContextFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Lcom/facebook/react/views/image/GlobalImageLoadListener;Ljava/lang/Object;)V - public fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Lcom/facebook/react/views/image/ReactCallerContextFactory;)V public fun (Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Ljava/lang/Object;)V public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View; public fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/image/ReactImageView; - public fun getCallerContext ()Ljava/lang/Object; - public fun getDraweeControllerBuilder ()Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder; public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map; public fun getName ()Ljava/lang/String; - protected synthetic fun onAfterUpdateTransaction (Landroid/view/View;)V - protected fun onAfterUpdateTransaction (Lcom/facebook/react/views/image/ReactImageView;)V - public fun setAccessible (Lcom/facebook/react/views/image/ReactImageView;Z)V - public fun setBlurRadius (Lcom/facebook/react/views/image/ReactImageView;F)V - public fun setBorderColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V - public fun setBorderRadius (Lcom/facebook/react/views/image/ReactImageView;IF)V - public fun setBorderWidth (Lcom/facebook/react/views/image/ReactImageView;F)V - public fun setDefaultSource (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V - public fun setFadeDuration (Lcom/facebook/react/views/image/ReactImageView;I)V - public fun setHeaders (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableMap;)V - public fun setInternal_AnalyticsTag (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V - public fun setLoadHandlersRegistered (Lcom/facebook/react/views/image/ReactImageView;Z)V - public fun setLoadingIndicatorSource (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V - public fun setOverlayColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V - public fun setProgressiveRenderingEnabled (Lcom/facebook/react/views/image/ReactImageView;Z)V - public fun setResizeMethod (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V - public fun setResizeMode (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V - public fun setResizeMultiplier (Lcom/facebook/react/views/image/ReactImageView;F)V - public fun setSource (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableArray;)V - public fun setSrc (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableArray;)V - public fun setTintColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V + public synthetic fun onAfterUpdateTransaction (Landroid/view/View;)V + public final fun setAccessible (Lcom/facebook/react/views/image/ReactImageView;Z)V + public final fun setBlurRadius (Lcom/facebook/react/views/image/ReactImageView;F)V + public final fun setBorderColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V + public final fun setBorderRadius (Lcom/facebook/react/views/image/ReactImageView;IF)V + public final fun setBorderWidth (Lcom/facebook/react/views/image/ReactImageView;F)V + public final fun setDefaultSource (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V + public final fun setFadeDuration (Lcom/facebook/react/views/image/ReactImageView;I)V + public final fun setHeaders (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableMap;)V + public final fun setInternal_AnalyticsTag (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V + public final fun setLoadHandlersRegistered (Lcom/facebook/react/views/image/ReactImageView;Z)V + public final fun setLoadingIndicatorSource (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V + public final fun setOverlayColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V + public final fun setProgressiveRenderingEnabled (Lcom/facebook/react/views/image/ReactImageView;Z)V + public final fun setResizeMethod (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V + public final fun setResizeMode (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V + public final fun setResizeMultiplier (Lcom/facebook/react/views/image/ReactImageView;F)V + public final fun setSource (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableArray;)V + public final fun setSrc (Lcom/facebook/react/views/image/ReactImageView;Lcom/facebook/react/bridge/ReadableArray;)V + public final fun setTintColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V } public class com/facebook/react/views/image/ReactImageManager$$PropsSetter : com/facebook/react/uimanager/ViewManagerPropertyUpdater$ViewManagerSetter { @@ -6399,6 +6405,9 @@ public class com/facebook/react/views/image/ReactImageManager$$PropsSetter : com public fun setProperty (Lcom/facebook/react/views/image/ReactImageManager;Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;Ljava/lang/Object;)V } +public final class com/facebook/react/views/image/ReactImageManager$Companion { +} + public class com/facebook/react/views/image/ReactImageView : com/facebook/drawee/view/GenericDraweeView { public static final field REMOTE_IMAGE_FADE_DURATION_MS I public fun (Landroid/content/Context;Lcom/facebook/drawee/controller/AbstractDraweeControllerBuilder;Lcom/facebook/react/views/image/GlobalImageLoadListener;Ljava/lang/Object;)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java index e36c378e4ee6..997d0165e24e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactNetworkImageRequest.java @@ -7,6 +7,7 @@ package com.facebook.react.modules.fresco; +import androidx.annotation.Nullable; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.react.bridge.ReadableMap; @@ -15,18 +16,19 @@ public class ReactNetworkImageRequest extends ImageRequest { /** Headers for the request */ - private final ReadableMap mHeaders; + @Nullable private final ReadableMap mHeaders; public static ReactNetworkImageRequest fromBuilderWithHeaders( - ImageRequestBuilder builder, ReadableMap headers) { + ImageRequestBuilder builder, @Nullable ReadableMap headers) { return new ReactNetworkImageRequest(builder, headers); } - protected ReactNetworkImageRequest(ImageRequestBuilder builder, ReadableMap headers) { + protected ReactNetworkImageRequest(ImageRequestBuilder builder, @Nullable ReadableMap headers) { super(builder); mHeaders = headers; } + @Nullable public ReadableMap getHeaders() { return mHeaders; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.java deleted file mode 100644 index 3c880f32105a..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.image; - -import android.graphics.Bitmap; -import com.facebook.cache.common.CacheKey; -import com.facebook.cache.common.MultiCacheKey; -import com.facebook.common.references.CloseableReference; -import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; -import com.facebook.imagepipeline.request.Postprocessor; -import java.util.LinkedList; -import java.util.List; - -public class MultiPostprocessor implements Postprocessor { - private final List mPostprocessors; - - public static Postprocessor from(List postprocessors) { - switch (postprocessors.size()) { - case 0: - return null; - case 1: - return postprocessors.get(0); - default: - return new MultiPostprocessor(postprocessors); - } - } - - private MultiPostprocessor(List postprocessors) { - mPostprocessors = new LinkedList<>(postprocessors); - } - - @Override - public String getName() { - StringBuilder name = new StringBuilder(); - for (Postprocessor p : mPostprocessors) { - if (name.length() > 0) { - name.append(","); - } - name.append(p.getName()); - } - name.insert(0, "MultiPostProcessor ("); - name.append(")"); - return name.toString(); - } - - @Override - public CacheKey getPostprocessorCacheKey() { - LinkedList keys = new LinkedList<>(); - for (Postprocessor p : mPostprocessors) { - keys.push(p.getPostprocessorCacheKey()); - } - return new MultiCacheKey(keys); - } - - @Override - public CloseableReference process( - Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) { - CloseableReference prevBitmap = null, nextBitmap = null; - - try { - for (Postprocessor p : mPostprocessors) { - nextBitmap = p.process(prevBitmap != null ? prevBitmap.get() : sourceBitmap, bitmapFactory); - CloseableReference.closeSafely(prevBitmap); - prevBitmap = nextBitmap.clone(); - } - return nextBitmap.clone(); - } finally { - CloseableReference.closeSafely(nextBitmap); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.kt new file mode 100644 index 000000000000..d0b7d48d5dce --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.image + +import android.graphics.Bitmap +import com.facebook.cache.common.CacheKey +import com.facebook.cache.common.MultiCacheKey +import com.facebook.common.references.CloseableReference +import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory +import com.facebook.imagepipeline.request.Postprocessor +import java.util.LinkedList + +public class MultiPostprocessor private constructor(postprocessors: List) : + Postprocessor { + + private val postprocessors: List = LinkedList(postprocessors) + + override fun getName(): String = "MultiPostProcessor (${postprocessors.joinToString(",")})" + + override fun getPostprocessorCacheKey(): CacheKey = + MultiCacheKey(postprocessors.map { it.postprocessorCacheKey }) + + public override fun process( + sourceBitmap: Bitmap, + bitmapFactory: PlatformBitmapFactory + ): CloseableReference { + var prevBitmap: CloseableReference? = null + var nextBitmap: CloseableReference? = null + + try { + for (p in postprocessors) { + nextBitmap = p.process(prevBitmap?.get() ?: sourceBitmap, bitmapFactory) + CloseableReference.closeSafely(prevBitmap) + prevBitmap = nextBitmap.clone() + } + checkNotNull(nextBitmap) { + ("MultiPostprocessor returned null bitmap - Number of Postprocessors: " + + postprocessors.size) + } + return nextBitmap.clone() + } finally { + CloseableReference.closeSafely(nextBitmap) + } + } + + public companion object { + @JvmStatic + public fun from(postprocessors: List): Postprocessor? { + return when (postprocessors.size) { + 0 -> null + 1 -> postprocessors[0] + else -> MultiPostprocessor(postprocessors) + } + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java deleted file mode 100644 index f9e66481c161..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.image; - -import android.graphics.Color; -import android.graphics.PorterDuff.Mode; -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.SimpleViewManager; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.ViewProps; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.annotations.ReactPropGroup; -import java.util.HashMap; -import java.util.Map; - -@ReactModule(name = ReactImageManager.REACT_CLASS) -public class ReactImageManager extends SimpleViewManager { - - public static final String REACT_CLASS = "RCTImageView"; - - private @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder; - private @Nullable GlobalImageLoadListener mGlobalImageLoadListener; - private final @Nullable Object mCallerContext; - private final @Nullable ReactCallerContextFactory mCallerContextFactory; - - /** - * @deprecated use {@link ReactImageManager#ReactImageManager(AbstractDraweeControllerBuilder, - * ReactCallerContextFactory)} instead. - */ - @Deprecated - public ReactImageManager( - @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder, - @Nullable Object callerContext) { - this(draweeControllerBuilder, null, callerContext); - } - - /** - * @deprecated use {@link ReactImageManager#ReactImageManager(AbstractDraweeControllerBuilder, - * GlobalImageLoadListener, ReactCallerContextFactory)} instead. - */ - @Deprecated - public ReactImageManager( - @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder, - @Nullable GlobalImageLoadListener globalImageLoadListener, - @Nullable Object callerContext) { - mDraweeControllerBuilder = draweeControllerBuilder; - mGlobalImageLoadListener = globalImageLoadListener; - mCallerContext = callerContext; - mCallerContextFactory = null; - } - - public ReactImageManager( - @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder, - @Nullable ReactCallerContextFactory callerContextFactory) { - this(draweeControllerBuilder, null, callerContextFactory); - } - - public ReactImageManager( - @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder, - @Nullable GlobalImageLoadListener globalImageLoadListener, - @Nullable ReactCallerContextFactory callerContextFactory) { - mDraweeControllerBuilder = draweeControllerBuilder; - mGlobalImageLoadListener = globalImageLoadListener; - mCallerContextFactory = callerContextFactory; - mCallerContext = null; - } - - public ReactImageManager() { - // Lazily initialize as FrescoModule have not been initialized yet - mDraweeControllerBuilder = null; - mCallerContext = null; - mCallerContextFactory = null; - } - - public AbstractDraweeControllerBuilder getDraweeControllerBuilder() { - if (mDraweeControllerBuilder == null) { - mDraweeControllerBuilder = Fresco.newDraweeControllerBuilder(); - } - return mDraweeControllerBuilder; - } - - /** - * @deprecated use {@link ReactCallerContextFactory} instead - */ - @Deprecated - public Object getCallerContext() { - return mCallerContext; - } - - @Override - public ReactImageView createViewInstance(ThemedReactContext context) { - Object callerContext = - mCallerContextFactory != null - ? mCallerContextFactory.getOrCreateCallerContext(context.getModuleName(), null) - : getCallerContext(); - return new ReactImageView( - context, getDraweeControllerBuilder(), mGlobalImageLoadListener, callerContext); - } - - @Override - public String getName() { - return REACT_CLASS; - } - - @ReactProp(name = "accessible") - public void setAccessible(ReactImageView view, boolean accessible) { - view.setFocusable(accessible); - } - - // In JS this is Image.props.source - @ReactProp(name = "src") - public void setSrc(ReactImageView view, @Nullable ReadableArray sources) { - setSource(view, sources); - } - - @ReactProp(name = "source") - public void setSource(ReactImageView view, @Nullable ReadableArray sources) { - view.setSource(sources); - } - - @ReactProp(name = "blurRadius") - public void setBlurRadius(ReactImageView view, float blurRadius) { - view.setBlurRadius(blurRadius); - } - - @ReactProp(name = "internal_analyticTag") - public void setInternal_AnalyticsTag(ReactImageView view, @Nullable String analyticTag) { - if (mCallerContextFactory != null) { - view.updateCallerContext( - mCallerContextFactory.getOrCreateCallerContext( - ((ThemedReactContext) view.getContext()).getModuleName(), analyticTag)); - } - } - - // In JS this is Image.props.defaultSource - @ReactProp(name = "defaultSrc") - public void setDefaultSource(ReactImageView view, @Nullable String source) { - view.setDefaultSource(source); - } - - // In JS this is Image.props.loadingIndicatorSource.uri - @ReactProp(name = "loadingIndicatorSrc") - public void setLoadingIndicatorSource(ReactImageView view, @Nullable String source) { - view.setLoadingIndicatorSource(source); - } - - @ReactProp(name = "borderColor", customType = "Color") - public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) { - if (borderColor == null) { - view.setBorderColor(Color.TRANSPARENT); - } else { - view.setBorderColor(borderColor); - } - } - - @ReactProp(name = "overlayColor", customType = "Color") - public void setOverlayColor(ReactImageView view, @Nullable Integer overlayColor) { - if (overlayColor == null) { - view.setOverlayColor(Color.TRANSPARENT); - } else { - view.setOverlayColor(overlayColor); - } - } - - @ReactProp(name = "borderWidth") - public void setBorderWidth(ReactImageView view, float borderWidth) { - view.setBorderWidth(borderWidth); - } - - @ReactPropGroup( - names = { - ViewProps.BORDER_RADIUS, - ViewProps.BORDER_TOP_LEFT_RADIUS, - ViewProps.BORDER_TOP_RIGHT_RADIUS, - ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, - ViewProps.BORDER_BOTTOM_LEFT_RADIUS - }, - defaultFloat = Float.NaN) - public void setBorderRadius(ReactImageView view, int index, float borderRadius) { - if (!Float.isNaN(borderRadius)) { - borderRadius = PixelUtil.toPixelFromDIP(borderRadius); - } - - if (index == 0) { - view.setBorderRadius(borderRadius); - } else { - view.setBorderRadius(borderRadius, index - 1); - } - } - - @ReactProp(name = ViewProps.RESIZE_MODE) - public void setResizeMode(ReactImageView view, @Nullable String resizeMode) { - view.setScaleType(ImageResizeMode.toScaleType(resizeMode)); - view.setTileMode(ImageResizeMode.toTileMode(resizeMode)); - } - - @ReactProp(name = ViewProps.RESIZE_METHOD) - public void setResizeMethod(ReactImageView view, @Nullable String resizeMethod) { - if (resizeMethod == null || "auto".equals(resizeMethod)) { - view.setResizeMethod(ImageResizeMethod.AUTO); - } else if ("resize".equals(resizeMethod)) { - view.setResizeMethod(ImageResizeMethod.RESIZE); - } else if ("scale".equals(resizeMethod)) { - view.setResizeMethod(ImageResizeMethod.SCALE); - } else { - view.setResizeMethod(ImageResizeMethod.AUTO); - FLog.w(ReactConstants.TAG, "Invalid resize method: '" + resizeMethod + "'"); - } - } - - @ReactProp(name = "resizeMultiplier") - public void setResizeMultiplier(ReactImageView view, float resizeMultiplier) { - if (resizeMultiplier < 0.01f) { - FLog.w(ReactConstants.TAG, "Invalid resize multiplier: '" + resizeMultiplier + "'"); - } - view.setResizeMultiplier(resizeMultiplier); - } - - @ReactProp(name = "tintColor", customType = "Color") - public void setTintColor(ReactImageView view, @Nullable Integer tintColor) { - if (tintColor == null) { - view.clearColorFilter(); - } else { - view.setColorFilter(tintColor, Mode.SRC_IN); - } - } - - @ReactProp(name = "progressiveRenderingEnabled") - public void setProgressiveRenderingEnabled(ReactImageView view, boolean enabled) { - view.setProgressiveRenderingEnabled(enabled); - } - - @ReactProp(name = "fadeDuration") - public void setFadeDuration(ReactImageView view, int durationMs) { - view.setFadeDuration(durationMs); - } - - @ReactProp(name = "shouldNotifyLoadEvents") - public void setLoadHandlersRegistered(ReactImageView view, boolean shouldNotifyLoadEvents) { - view.setShouldNotifyLoadEvents(shouldNotifyLoadEvents); - } - - @ReactProp(name = "headers") - public void setHeaders(ReactImageView view, ReadableMap headers) { - view.setHeaders(headers); - } - - @Override - public @Nullable Map getExportedCustomDirectEventTypeConstants() { - @Nullable - Map baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants(); - Map eventTypeConstants = - baseEventTypeConstants == null ? new HashMap() : baseEventTypeConstants; - eventTypeConstants.putAll( - MapBuilder.of( - ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD_START), - MapBuilder.of("registrationName", "onLoadStart"), - ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_PROGRESS), - MapBuilder.of("registrationName", "onProgress"), - ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD), - MapBuilder.of("registrationName", "onLoad"), - ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_ERROR), - MapBuilder.of("registrationName", "onError"), - ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD_END), - MapBuilder.of("registrationName", "onLoadEnd"))); - return eventTypeConstants; - } - - @Override - protected void onAfterUpdateTransaction(ReactImageView view) { - super.onAfterUpdateTransaction(view); - view.maybeUpdateView(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt new file mode 100644 index 000000000000..ba5c03a29129 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt @@ -0,0 +1,272 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.image + +import android.graphics.Color +import android.graphics.PorterDuff +import com.facebook.common.logging.FLog +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.drawee.controller.AbstractDraweeControllerBuilder +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.common.MapBuilder +import com.facebook.react.common.ReactConstants +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewProps +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.uimanager.annotations.ReactPropGroup +import com.facebook.react.views.image.ImageLoadEvent.Companion.eventNameForType +import com.facebook.react.views.image.ImageResizeMode.toScaleType +import com.facebook.react.views.image.ImageResizeMode.toTileMode + +@ReactModule(name = ReactImageManager.REACT_CLASS) +public class ReactImageManager +@JvmOverloads +public constructor( + private val draweeControllerBuilder: AbstractDraweeControllerBuilder<*, *, *, *>? = null, + private val globalImageLoadListener: GlobalImageLoadListener? = null, + private val callerContextFactory: ReactCallerContextFactory? = null +) : SimpleViewManager() { + + // This is kept for backward compatibility but should eventually be removed together with + // the constructor. + private var callerContext: Any? = null + + /** + * Alternative constructor which allows to provide a callerContext as an [Object] + * + * @deprecated Use the constructor with ReactCallerContextFactory instead + */ + @Deprecated( + message = "Use the constructor with ReactCallerContextFactory instead", + replaceWith = + ReplaceWith( + expression = + "ReactImageManager(draweeControllerBuilder, globalImageLoadListener, callerContextFactory)")) + public constructor( + draweeControllerBuilder: AbstractDraweeControllerBuilder<*, *, *, *>?, + callerContext: Any? + ) : this(draweeControllerBuilder, null, null) { + this.callerContext = callerContext + } + + /** + * Alternative constructor which allows to provide a callerContext as an [Object] + * + * @deprecated Use the constructor with ReactCallerContextFactory instead + */ + @Deprecated( + message = "Use the constructor with ReactCallerContextFactory instead", + replaceWith = + ReplaceWith( + expression = + "ReactImageManager(draweeControllerBuilder, globalImageLoadListener, callerContextFactory)")) + public constructor( + draweeControllerBuilder: AbstractDraweeControllerBuilder<*, *, *, *>?, + globalImageLoadListener: GlobalImageLoadListener?, + callerContext: Any? + ) : this(draweeControllerBuilder, globalImageLoadListener, null) { + this.callerContext = callerContext + } + + public override fun createViewInstance(context: ThemedReactContext): ReactImageView { + val callerContext = + this.callerContext + ?: callerContextFactory?.getOrCreateCallerContext(context.moduleName, null) + return ReactImageView( + context, + draweeControllerBuilder ?: Fresco.newDraweeControllerBuilder(), + globalImageLoadListener, + callerContext) + } + + public override fun getName(): String = REACT_CLASS + + @ReactProp(name = "accessible") + public fun setAccessible(view: ReactImageView, accessible: Boolean) { + view.isFocusable = accessible + } + + // In JS this is Image.props.source + @ReactProp(name = "src") + public fun setSrc(view: ReactImageView, sources: ReadableArray?) { + setSource(view, sources) + } + + @ReactProp(name = "source") + public fun setSource(view: ReactImageView, sources: ReadableArray?) { + view.setSource(sources) + } + + @ReactProp(name = "blurRadius") + public fun setBlurRadius(view: ReactImageView, blurRadius: Float) { + view.setBlurRadius(blurRadius) + } + + @Suppress("FunctionName") + @ReactProp(name = "internal_analyticTag") + public fun setInternal_AnalyticsTag(view: ReactImageView, analyticTag: String?) { + if (callerContextFactory != null) { + view.updateCallerContext( + callerContextFactory.getOrCreateCallerContext( + (view.context as ThemedReactContext).moduleName, analyticTag)) + } + } + + // In JS this is Image.props.defaultSource + @ReactProp(name = "defaultSrc") + public fun setDefaultSource(view: ReactImageView, source: String?) { + view.setDefaultSource(source) + } + + // In JS this is Image.props.loadingIndicatorSource.uri + @ReactProp(name = "loadingIndicatorSrc") + public fun setLoadingIndicatorSource(view: ReactImageView, source: String?) { + view.setLoadingIndicatorSource(source) + } + + @ReactProp(name = "borderColor", customType = "Color") + public fun setBorderColor(view: ReactImageView, borderColor: Int?) { + if (borderColor == null) { + view.setBorderColor(Color.TRANSPARENT) + } else { + view.setBorderColor(borderColor) + } + } + + @ReactProp(name = "overlayColor", customType = "Color") + public fun setOverlayColor(view: ReactImageView, overlayColor: Int?) { + if (overlayColor == null) { + view.setOverlayColor(Color.TRANSPARENT) + } else { + view.setOverlayColor(overlayColor) + } + } + + @ReactProp(name = "borderWidth") + public fun setBorderWidth(view: ReactImageView, borderWidth: Float) { + view.setBorderWidth(borderWidth) + } + + @ReactPropGroup( + names = + [ + ViewProps.BORDER_RADIUS, + ViewProps.BORDER_TOP_LEFT_RADIUS, + ViewProps.BORDER_TOP_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_LEFT_RADIUS], + defaultFloat = Float.NaN) + public fun setBorderRadius(view: ReactImageView, index: Int, borderRadius: Float) { + val convertedBorderRadius = + if (!borderRadius.isNaN()) { + toPixelFromDIP(borderRadius) + } else { + borderRadius + } + if (index == 0) { + view.setBorderRadius(convertedBorderRadius) + } else { + view.setBorderRadius(convertedBorderRadius, index - 1) + } + } + + @ReactProp(name = ViewProps.RESIZE_MODE) + public fun setResizeMode(view: ReactImageView, resizeMode: String?) { + view.setScaleType(toScaleType(resizeMode)) + view.setTileMode(toTileMode(resizeMode)) + } + + @ReactProp(name = ViewProps.RESIZE_METHOD) + public fun setResizeMethod(view: ReactImageView, resizeMethod: String?) { + when (resizeMethod) { + null, + "auto" -> view.setResizeMethod(ImageResizeMethod.AUTO) + + "resize" -> view.setResizeMethod(ImageResizeMethod.RESIZE) + "scale" -> view.setResizeMethod(ImageResizeMethod.SCALE) + else -> { + view.setResizeMethod(ImageResizeMethod.AUTO) + FLog.w(ReactConstants.TAG, "Invalid resize method: '$resizeMethod'") + } + } + } + + @ReactProp(name = "resizeMultiplier") + public fun setResizeMultiplier(view: ReactImageView, resizeMultiplier: Float) { + if (resizeMultiplier < 0.01f) { + FLog.w(ReactConstants.TAG, "Invalid resize multiplier: '$resizeMultiplier'") + } + view.setResizeMultiplier(resizeMultiplier) + } + + @ReactProp(name = "tintColor", customType = "Color") + public fun setTintColor(view: ReactImageView, tintColor: Int?) { + if (tintColor == null) { + view.clearColorFilter() + } else { + view.setColorFilter(tintColor, PorterDuff.Mode.SRC_IN) + } + } + + @ReactProp(name = "progressiveRenderingEnabled") + public fun setProgressiveRenderingEnabled(view: ReactImageView, enabled: Boolean) { + view.setProgressiveRenderingEnabled(enabled) + } + + @ReactProp(name = "fadeDuration") + public fun setFadeDuration(view: ReactImageView, durationMs: Int) { + view.setFadeDuration(durationMs) + } + + @ReactProp(name = "shouldNotifyLoadEvents") + public fun setLoadHandlersRegistered(view: ReactImageView, shouldNotifyLoadEvents: Boolean) { + view.setShouldNotifyLoadEvents(shouldNotifyLoadEvents) + } + + @ReactProp(name = "headers") + public fun setHeaders(view: ReactImageView, headers: ReadableMap?) { + if (headers != null) { + view.setHeaders(headers) + } + } + + public override fun getExportedCustomDirectEventTypeConstants(): MutableMap = + (super.getExportedCustomDirectEventTypeConstants() ?: mutableMapOf()).apply { + put( + eventNameForType(ImageLoadEvent.ON_LOAD_START), + MapBuilder.of(REGISTRATION_NAME, ON_LOAD_START)) + put( + eventNameForType(ImageLoadEvent.ON_PROGRESS), + MapBuilder.of(REGISTRATION_NAME, ON_PROGRESS)) + put(eventNameForType(ImageLoadEvent.ON_LOAD), MapBuilder.of(REGISTRATION_NAME, ON_LOAD)) + put(eventNameForType(ImageLoadEvent.ON_ERROR), MapBuilder.of(REGISTRATION_NAME, ON_ERROR)) + put( + eventNameForType(ImageLoadEvent.ON_LOAD_END), + MapBuilder.of(REGISTRATION_NAME, ON_LOAD_END)) + } + + protected override fun onAfterUpdateTransaction(view: ReactImageView) { + super.onAfterUpdateTransaction(view) + view.maybeUpdateView() + } + + public companion object { + public const val REACT_CLASS: String = "RCTImageView" + + private const val REGISTRATION_NAME: String = "registrationName" + private const val ON_LOAD_START: String = "onLoadStart" + private const val ON_PROGRESS: String = "onProgress" + private const val ON_LOAD: String = "onLoad" + private const val ON_ERROR: String = "onError" + private const val ON_LOAD_END: String = "onLoadEnd" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index 66e668e12393..a90bacd76458 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -41,6 +41,7 @@ import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.Postprocessor; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -68,6 +69,7 @@ * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view * update and consistent processing of both static and network images. */ +@Nullsafe(Nullsafe.Mode.LOCAL) public class ReactImageView extends GenericDraweeView { public static final int REMOTE_IMAGE_FADE_DURATION_MS = 300; @@ -136,7 +138,7 @@ public CloseableReference process(Bitmap source, PlatformBitmapFactory b private @Nullable Object mCallerContext; private int mFadeDurationMs = -1; private boolean mProgressiveRenderingEnabled; - private ReadableMap mHeaders; + private @Nullable ReadableMap mHeaders = null; private float mResizeMultiplier = 1.0f; private ReactViewBackgroundManager mReactBackgroundManager; @@ -182,6 +184,9 @@ public void setShouldNotifyLoadEvents(boolean shouldNotify) { @Override public void onProgressChange(int loaded, int total) { // TODO: Somehow get image size and convert `loaded` and `total` to image bytes. + if (mEventDispatcher == null || mImageSource == null) { + return; + } mEventDispatcher.dispatchEvent( ImageLoadEvent.createProgressEvent( UIManagerHelper.getSurfaceId(ReactImageView.this), @@ -193,6 +198,9 @@ public void onProgressChange(int loaded, int total) { @Override public void onSubmit(String id, Object callerContext) { + if (mEventDispatcher == null) { + return; + } mEventDispatcher.dispatchEvent( ImageLoadEvent.createLoadStartEvent( UIManagerHelper.getSurfaceId(ReactImageView.this), getId())); @@ -201,7 +209,7 @@ public void onSubmit(String id, Object callerContext) { @Override public void onFinalImageSet( String id, @Nullable final ImageInfo imageInfo, @Nullable Animatable animatable) { - if (imageInfo != null) { + if (imageInfo != null && mEventDispatcher != null && mImageSource != null) { mEventDispatcher.dispatchEvent( ImageLoadEvent.createLoadEvent( UIManagerHelper.getSurfaceId(ReactImageView.this), @@ -217,6 +225,9 @@ public void onFinalImageSet( @Override public void onFailure(String id, Throwable throwable) { + if (mEventDispatcher == null) { + return; + } mEventDispatcher.dispatchEvent( ImageLoadEvent.createErrorEvent( UIManagerHelper.getSurfaceId(ReactImageView.this), getId(), throwable)); @@ -472,25 +483,27 @@ public void maybeUpdateView() { getCornerRadii(sComputedCornerRadii); RoundingParams roundingParams = hierarchy.getRoundingParams(); - roundingParams.setCornersRadii( - sComputedCornerRadii[0], - sComputedCornerRadii[1], - sComputedCornerRadii[2], - sComputedCornerRadii[3]); - - if (mBackgroundImageDrawable != null) { - mBackgroundImageDrawable.setBorder(mBorderColor, mBorderWidth); - mBackgroundImageDrawable.setRadii(roundingParams.getCornersRadii()); - hierarchy.setBackgroundImage(mBackgroundImageDrawable); - } - roundingParams.setBorder(mBorderColor, mBorderWidth); - if (mOverlayColor != Color.TRANSPARENT) { - roundingParams.setOverlayColor(mOverlayColor); - } else { - // make sure the default rounding method is used. - roundingParams.setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY); + if (roundingParams != null) { + roundingParams.setCornersRadii( + sComputedCornerRadii[0], + sComputedCornerRadii[1], + sComputedCornerRadii[2], + sComputedCornerRadii[3]); + + if (mBackgroundImageDrawable != null) { + mBackgroundImageDrawable.setBorder(mBorderColor, mBorderWidth); + mBackgroundImageDrawable.setRadii(roundingParams.getCornersRadii()); + hierarchy.setBackgroundImage(mBackgroundImageDrawable); + } + roundingParams.setBorder(mBorderColor, mBorderWidth); + if (mOverlayColor != Color.TRANSPARENT) { + roundingParams.setOverlayColor(mOverlayColor); + } else { + // make sure the default rounding method is used. + roundingParams.setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY); + } + hierarchy.setRoundingParams(roundingParams); } - hierarchy.setRoundingParams(roundingParams); hierarchy.setFadeDuration( mFadeDurationMs >= 0 ? mFadeDurationMs @@ -507,6 +520,10 @@ public void maybeUpdateView() { } private void maybeUpdateViewFromRequest(boolean doResize) { + if (mImageSource == null) { + return; + } + List postprocessors = new LinkedList<>(); if (mIterativeBoxBlurPostProcessor != null) { postprocessors.add(mIterativeBoxBlurPostProcessor); @@ -537,10 +554,13 @@ private void maybeUpdateViewFromRequest(boolean doResize) { mDraweeControllerBuilder .setAutoPlayAnimations(true) - .setCallerContext(mCallerContext) .setOldController(getController()) .setImageRequest(imageRequest); + if (mCallerContext != null) { + mDraweeControllerBuilder.setCallerContext(mCallerContext); + } + if (mCachedImageSource != null) { ImageRequest cachedImageRequest = ImageRequestBuilder.newBuilderWithSource(mCachedImageSource.getUri()) @@ -576,6 +596,7 @@ private void maybeUpdateViewFromRequest(boolean doResize) { private void maybeUpdateViewFromDrawable(Drawable drawable) { final boolean shouldNotify = mDownloadListener != null; + final EventDispatcher mEventDispatcher = shouldNotify ? UIManagerHelper.getEventDispatcherForReactTag((ReactContext) getContext(), getId()) @@ -589,7 +610,7 @@ private void maybeUpdateViewFromDrawable(Drawable drawable) { getHierarchy().setImage(drawable, 1, false); - if (mEventDispatcher != null) { + if (mEventDispatcher != null && mImageSource != null) { mEventDispatcher.dispatchEvent( ImageLoadEvent.createLoadEvent( UIManagerHelper.getSurfaceId(ReactImageView.this), @@ -708,7 +729,7 @@ private ResizeOptions getResizeOptions() { return new ResizeOptions(width, height); } - private void warnImageSource(String uri) { + private void warnImageSource(@Nullable String uri) { // TODO(T189014077): This code-path produces an infinite loop of js calls with logbox. // This is an issue with Fabric view preallocation, react, and LogBox. Fix. // The bug: