diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/BitmapUpdateListener.java b/ReactAndroid/src/main/java/com/facebook/react/flat/BitmapUpdateListener.java index 115c20fa8..16432e0be 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/BitmapUpdateListener.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/BitmapUpdateListener.java @@ -14,4 +14,5 @@ import android.graphics.Bitmap; /* package */ interface BitmapUpdateListener { public void onSecondaryAttach(Bitmap bitmap); public void onBitmapReady(Bitmap bitmap); + public void onImageLoadEvent(int imageLoadEvent); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java index 4ca727afe..40710c9c5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java @@ -44,6 +44,11 @@ import com.facebook.imagepipeline.request.ImageRequest; */ public ScaleType getScaleType(); + /** + * React tag used for dispatching ImageLoadEvents, or 0 to ignore events. + */ + public void setReactTag(int reactTag); + public void setBorderWidth(float borderWidth); public float getBorderWidth(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java index b1cf59541..c52cb1cdb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java @@ -14,19 +14,23 @@ import javax.annotation.Nullable; import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Animatable; +import com.facebook.drawee.controller.ControllerListener; import com.facebook.drawee.drawable.ScalingUtils.ScaleType; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.RoundingParams; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.views.image.ImageLoadEvent; import com.facebook.react.views.image.ImageResizeMode; /** * DrawImageWithDrawee is DrawCommand that can draw a local or remote image. * It uses DraweeRequestHelper internally to fetch and cache the images. */ -/* package */ final class DrawImageWithDrawee extends AbstractDrawCommand implements DrawImage { +/* package */ final class DrawImageWithDrawee extends AbstractDrawCommand + implements DrawImage, ControllerListener { private @Nullable DraweeRequestHelper mRequestHelper; private @Nullable PorterDuffColorFilter mColorFilter; @@ -34,6 +38,8 @@ import com.facebook.react.views.image.ImageResizeMode; private float mBorderWidth; private float mBorderRadius; private int mBorderColor; + private int mReactTag; + private @Nullable FlatViewGroup.InvalidateCallback mCallback; @Override public boolean hasImageRequest() { @@ -45,7 +51,7 @@ import com.facebook.react.views.image.ImageResizeMode; if (imageRequest == null) { mRequestHelper = null; } else { - mRequestHelper = new DraweeRequestHelper(imageRequest); + mRequestHelper = new DraweeRequestHelper(imageRequest, this); } } @@ -98,6 +104,11 @@ import com.facebook.react.views.image.ImageResizeMode; return mBorderColor; } + @Override + public void setReactTag(int reactTag) { + mReactTag = reactTag; + } + @Override public void onDraw(Canvas canvas) { Assertions.assumeNotNull(mRequestHelper).getDrawable().draw(canvas); @@ -105,6 +116,8 @@ import com.facebook.react.views.image.ImageResizeMode; @Override public void onAttached(FlatViewGroup.InvalidateCallback callback) { + mCallback = callback; + GenericDraweeHierarchy hierarchy = Assertions.assumeNotNull(mRequestHelper).getHierarchy(); RoundingParams roundingParams = hierarchy.getRoundingParams(); @@ -140,6 +153,43 @@ import com.facebook.react.views.image.ImageResizeMode; Assertions.assumeNotNull(mRequestHelper).detach(); } + @Override + public void onSubmit(String id, Object callerContext) { + if (mCallback != null && mReactTag != 0) { + mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_START); + } + } + + @Override + public void onFinalImageSet( + String id, + @Nullable Object imageInfo, + @Nullable Animatable animatable) { + if (mCallback != null && mReactTag != 0) { + mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END); + mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD); + } + } + + @Override + public void onIntermediateImageSet(String id, @Nullable Object imageInfo) { + } + + @Override + public void onIntermediateImageFailed(String id, Throwable throwable) { + } + + @Override + public void onFailure(String id, Throwable throwable) { + if (mCallback != null && mReactTag != 0) { + mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END); + } + } + + @Override + public void onRelease(String id) { + } + private boolean shouldDisplayBorder() { return mBorderColor != 0 || mBorderRadius >= 0.5f; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java index e4fba1b77..7a987fa88 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java @@ -44,6 +44,7 @@ import com.facebook.react.views.image.ImageResizeMode; private @Nullable Path mPathForRoundedBitmap; private @Nullable BitmapShader mBitmapShader; private boolean mForceClip; + private int mReactTag; @Override public boolean hasImageRequest() { @@ -80,6 +81,11 @@ import com.facebook.react.views.image.ImageResizeMode; return mScaleType; } + @Override + public void setReactTag(int reactTag) { + mReactTag = reactTag; + } + @Override protected void onDraw(Canvas canvas) { Bitmap bitmap = Assertions.assumeNotNull(mRequestHelper).getBitmap(); @@ -148,6 +154,13 @@ import com.facebook.react.views.image.ImageResizeMode; updateBounds(bitmap); } + @Override + public void onImageLoadEvent(int imageLoadEvent) { + if (mReactTag != 0 && mCallback != null) { + mCallback.dispatchImageLoadEvent(mReactTag, imageLoadEvent); + } + } + /* package */ void updateBounds(Bitmap bitmap) { Assertions.assumeNotNull(mCallback).invalidate(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DraweeRequestHelper.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DraweeRequestHelper.java index 1b1c93d26..2fcd06979 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DraweeRequestHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DraweeRequestHelper.java @@ -14,6 +14,7 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; +import com.facebook.drawee.controller.ControllerListener; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.interfaces.DraweeController; @@ -37,10 +38,11 @@ import com.facebook.infer.annotation.Assertions; private final DraweeController mDraweeController; private int mAttachCounter; - /* package */ DraweeRequestHelper(ImageRequest imageRequest) { + /* package */ DraweeRequestHelper(ImageRequest imageRequest, ControllerListener listener) { DraweeController controller = sControllerBuilder .setImageRequest(imageRequest) .setCallerContext(RCTImageView.getCallerContext()) + .setControllerListener(listener) .build(); controller.setHierarchy(sHierarchyBuilder.build()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java index 3bbb88da4..7e0df6ca8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java @@ -18,17 +18,21 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.touch.CatalystInterceptingViewGroup; import com.facebook.react.touch.OnInterceptTouchEventListener; import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.ReactCompoundView; import com.facebook.react.uimanager.ReactPointerEventsView; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.views.image.ImageLoadEvent; /** * A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over @@ -54,6 +58,18 @@ import com.facebook.react.uimanager.ReactPointerEventsView; view.invalidate(); } } + + public void dispatchImageLoadEvent(int reactTag, int imageLoadEvent) { + FlatViewGroup view = get(); + if (view == null) { + return; + } + + ReactContext reactContext = ((ReactContext) view.getContext()); + UIManagerModule uiManagerModule = reactContext.getNativeModule(UIManagerModule.class); + uiManagerModule.getEventDispatcher().dispatchEvent( + new ImageLoadEvent(reactTag, SystemClock.uptimeMillis(), imageLoadEvent)); + } } private static final ArrayList LAYOUT_REQUESTS = new ArrayList<>(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/InlineImageSpanWithPipeline.java b/ReactAndroid/src/main/java/com/facebook/react/flat/InlineImageSpanWithPipeline.java index b0d197690..6c90e4f1b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/InlineImageSpanWithPipeline.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/InlineImageSpanWithPipeline.java @@ -99,6 +99,11 @@ import com.facebook.infer.annotation.Assertions; Assertions.assumeNotNull(mCallback).invalidate(); } + @Override + public void onImageLoadEvent(int imageLoadEvent) { + // ignore + } + @Override public void onAttached(FlatViewGroup.InvalidateCallback callback) { mCallback = callback; diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/PipelineRequestHelper.java b/ReactAndroid/src/main/java/com/facebook/react/flat/PipelineRequestHelper.java index f30983bb2..92beb10c5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/PipelineRequestHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/PipelineRequestHelper.java @@ -23,6 +23,7 @@ import com.facebook.imagepipeline.image.CloseableBitmap; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.views.image.ImageLoadEvent; /** * Helper class for DrawImage that helps manage fetch requests through ImagePipeline. @@ -54,11 +55,13 @@ import com.facebook.infer.annotation.Assertions; // this is a secondary attach, ignore it, only updating Bitmap boundaries if needed. Bitmap bitmap = getBitmap(); if (bitmap != null) { - mBitmapUpdateListener.onSecondaryAttach(bitmap); + listener.onSecondaryAttach(bitmap); } return; } + listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_START); + Assertions.assertCondition(mDataSource == null); Assertions.assertCondition(mImageRef == null); @@ -150,7 +153,10 @@ import com.facebook.infer.annotation.Assertions; return; } - Assertions.assumeNotNull(mBitmapUpdateListener).onBitmapReady(bitmap); + BitmapUpdateListener listener = Assertions.assumeNotNull(mBitmapUpdateListener); + listener.onBitmapReady(bitmap); + listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_END); + listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD); } finally { dataSource.close(); } @@ -158,8 +164,8 @@ import com.facebook.infer.annotation.Assertions; @Override public void onFailure(DataSource> dataSource) { - if (mDataSource != dataSource) { - // Should always be the case, but let's be safe. + if (mDataSource == dataSource) { + Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_LOAD_END); mDataSource = null; } @@ -168,6 +174,11 @@ import com.facebook.infer.annotation.Assertions; @Override public void onCancellation(DataSource> dataSource) { + if (mDataSource == dataSource) { + mDataSource = null; + } + + dataSource.close(); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java index 248891df6..75d3abdb8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java @@ -80,6 +80,11 @@ import com.facebook.react.views.image.ImageResizeMode; } } + @ReactProp(name = "shouldNotifyLoadEvents") + public void setShouldNotifyLoadEvents(boolean shouldNotifyLoadEvents) { + getMutableDrawImage().setReactTag(shouldNotifyLoadEvents ? getReactTag() : 0); + } + @ReactProp(name = "src") public void setSource(@Nullable String source) { getMutableDrawImage().setImageRequest(