Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: FrescoBasedTextInlineImageSpan #50532

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* 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.text.frescosupport

import android.content.res.Resources
import android.graphics.BlendMode
import android.graphics.BlendModeColorFilter
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.widget.TextView
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder
import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder
import com.facebook.drawee.view.DraweeHolder
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.modules.fresco.ReactNetworkImageRequest
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.views.image.ImageResizeMode
import com.facebook.react.views.text.internal.span.TextInlineImageSpan

/**
* FrescoBasedTextInlineImageSpan is a span for Images that are inside <Text/>. It computes its size
* based on the input size. When it is time to draw, it will use the Fresco framework to get the
* right Drawable and let that draw.
*
* Since Fresco needs to callback to the TextView that contains this, in the ViewManager, you must
* tell the Span about the TextView
*
* Note: It borrows code from DynamicDrawableSpan and if that code updates how it computes size or
* draws, we need to update this as well.
*/
internal class FrescoBasedReactTextInlineImageSpan(
resources: Resources,
height: Int,
width: Int,
private val tintColor: Int,
uri: Uri?,
private val headers: ReadableMap?,
private val draweeControllerBuilder: AbstractDraweeControllerBuilder<*, ImageRequest, *, *>,
private val callerContext: Any?,
private val resizeMode: String?
) : TextInlineImageSpan() {

private var textView: TextView? = null
private val _uri: Uri = uri ?: Uri.EMPTY
private val _width: Int = PixelUtil.toPixelFromDIP(width.toDouble()).toInt()
private val _height: Int = PixelUtil.toPixelFromDIP(height.toDouble()).toInt()
private val draweeHolder: DraweeHolder<GenericDraweeHierarchy> =
DraweeHolder(GenericDraweeHierarchyBuilder.newInstance(resources).build())

override val width: Int
get() = _width

override val height: Int
get() = _height

override var drawable: Drawable? = null
private set

/**
* The ReactTextView that holds this ImageSpan is responsible for passing these methods on so
* that we can do proper lifetime management for Fresco
*/
public override fun onDetachedFromWindow() {
draweeHolder.onDetach()
}

public override fun onStartTemporaryDetach() {
draweeHolder.onDetach()
}

public override fun onAttachedToWindow() {
draweeHolder.onAttach()
}

public override fun onFinishTemporaryDetach() {
draweeHolder.onAttach()
}

public override fun getSize(
paint: Paint,
text: CharSequence,
start: Int,
end: Int,
fm: Paint.FontMetricsInt?
): Int {
// NOTE: This getSize code is copied from DynamicDrawableSpan and modified
// to not use a Drawable

fm?.let {
it.ascent = -_height
it.descent = 0

it.top = it.ascent
it.bottom = 0
}

return _width
}

public override fun setTextView(textView: TextView?) {
this.textView = textView
}

public override fun draw(
canvas: Canvas,
text: CharSequence,
start: Int,
end: Int,
x: Float,
top: Int,
y: Int,
bottom: Int,
paint: Paint
) {
if (drawable == null) {
val imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(_uri)
val imageRequest: ImageRequest =
ReactNetworkImageRequest.fromBuilderWithHeaders(imageRequestBuilder, headers)

draweeHolder.hierarchy.setActualImageScaleType(ImageResizeMode.toScaleType(resizeMode))

draweeControllerBuilder.reset()
draweeControllerBuilder.oldController = draweeHolder.controller

callerContext?.let { draweeControllerBuilder.setCallerContext(it) }

draweeControllerBuilder.setImageRequest(imageRequest)

val draweeController = draweeControllerBuilder.build()
draweeHolder.controller = draweeController
draweeControllerBuilder.reset()

checkNotNull(draweeHolder.topLevelDrawable).apply {
setBounds(0, 0, _width, _height)
if (tintColor != 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
colorFilter = BlendModeColorFilter(tintColor, BlendMode.SRC_IN)
} else {
setColorFilter(tintColor, PorterDuff.Mode.SRC_IN)
}
}
callback = textView
drawable = this
}
}

// NOTE: This drawing code is copied from DynamicDrawableSpan

canvas.save()

// Align to center
val _drawable = checkNotNull(drawable)
val fontHeight = (paint.descent() - paint.ascent()).toInt()
val centerY = y + paint.descent().toInt() - fontHeight / 2
val transY = centerY - _drawable.bounds.height() / 2

canvas.translate(x, transY.toFloat())
_drawable.draw(canvas)
canvas.restore()
}
}
Loading