Skip to content

Commit

Permalink
Extract textmapper logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Dec 12, 2023
1 parent a87b2e8 commit 68aa3f2
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 249 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.datadog.reactnative.sessionreplay

import android.view.Gravity
import android.widget.TextView
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
import com.datadog.reactnative.sessionreplay.extensions.densityNormalized
import com.facebook.react.views.text.ReactTextShadowNode

internal class ReactTextPropertiesResolver(
private val reactTextShadowNodeUtils: ReactTextShadowNodeUtils
) {
internal fun resolveShadowProperties(
view: TextView,
pixelDensity: Float,
shapeStyle: MobileSegment.ShapeStyle?,
border: MobileSegment.ShapeBorder?,
textWireframe: MobileSegment.Wireframe.TextWireframe
): MobileSegment.Wireframe {

val shadowNode = reactTextShadowNodeUtils.getShadowNode(view.id) as? ReactTextShadowNode

val textStyle = resolveTextStyle(view, pixelDensity, shadowNode)
val textPosition = resolveTextPosition(view, pixelDensity, shadowNode)

return textWireframe.copy(
shapeStyle = shapeStyle,
border = border,
textStyle = textStyle,
textPosition = textPosition
)
}

private fun resolveTextPosition(
view: TextView,
pixelsDensity: Float,
shadowNode: ReactTextShadowNode?
): MobileSegment.TextPosition {
return MobileSegment.TextPosition(
resolvePadding(view, pixelsDensity),
resolveAlignment(view, shadowNode)
)
}

private fun resolveTextStyle(
view: TextView,
pixelsDensity: Float,
shadowNode: ReactTextShadowNode?
): MobileSegment.TextStyle {
return MobileSegment.TextStyle(
family = reactTextShadowNodeUtils.getFontFamily(view, shadowNode),
size = reactTextShadowNodeUtils.getFontSize(view, shadowNode).convertToDensityNormalized(pixelsDensity),
color = resolveTextColor(view, shadowNode)
)
}

private fun resolvePadding(textView: TextView, pixelsDensity: Float): MobileSegment.Padding {
return MobileSegment.Padding(
top = textView.totalPaddingTop.densityNormalized(pixelsDensity).toLong(),
bottom = textView.totalPaddingBottom.densityNormalized(pixelsDensity).toLong(),
left = textView.totalPaddingStart.densityNormalized(pixelsDensity).toLong(),
right = textView.totalPaddingEnd.densityNormalized(pixelsDensity).toLong()
)
}

private fun resolveAlignment(
textView: TextView,
shadowNode: ReactTextShadowNode?
): MobileSegment.Alignment {
return when (textView.textAlignment) {
TextView.TEXT_ALIGNMENT_GRAVITY -> resolveShadowAlignment(textView, shadowNode = shadowNode)
TextView.TEXT_ALIGNMENT_CENTER -> MobileSegment.Alignment(
horizontal = MobileSegment.Horizontal.CENTER,
vertical = MobileSegment.Vertical.CENTER
)

TextView.TEXT_ALIGNMENT_TEXT_END,
TextView.TEXT_ALIGNMENT_VIEW_END -> MobileSegment.Alignment(
horizontal = MobileSegment.Horizontal.RIGHT,
vertical = MobileSegment.Vertical.CENTER
)

TextView.TEXT_ALIGNMENT_TEXT_START,
TextView.TEXT_ALIGNMENT_VIEW_START -> MobileSegment.Alignment(
horizontal = MobileSegment.Horizontal.LEFT,
vertical = MobileSegment.Vertical.CENTER
)

else -> MobileSegment.Alignment(
horizontal = MobileSegment.Horizontal.LEFT,
vertical = MobileSegment.Vertical.CENTER
)
}
}

private fun resolveShadowAlignment(
view: TextView,
shadowNode: ReactTextShadowNode?
): MobileSegment.Alignment {

val shadowGravity = reactTextShadowNodeUtils.getGravity(view, shadowNode)


val horizontalAlignment = when (shadowGravity.and(Gravity.HORIZONTAL_GRAVITY_MASK)) {
Gravity.START,
Gravity.LEFT -> MobileSegment.Horizontal.LEFT

Gravity.END,
Gravity.RIGHT -> MobileSegment.Horizontal.RIGHT

Gravity.CENTER,
Gravity.CENTER_HORIZONTAL -> MobileSegment.Horizontal.CENTER

else -> MobileSegment.Horizontal.LEFT
}
val verticalAlignment = when (shadowGravity.and(Gravity.VERTICAL_GRAVITY_MASK)) {
Gravity.TOP -> MobileSegment.Vertical.TOP
Gravity.BOTTOM -> MobileSegment.Vertical.BOTTOM

Gravity.CENTER_VERTICAL,
Gravity.CENTER -> MobileSegment.Vertical.CENTER

else -> MobileSegment.Vertical.CENTER
}

return MobileSegment.Alignment(horizontalAlignment, verticalAlignment)
}

private fun resolveTextColor(view: TextView, shadowNode: ReactTextShadowNode?): String {
return formatAsRgba(reactTextShadowNodeUtils.getColor(view, shadowNode))
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.datadog.reactnative.sessionreplay

import android.annotation.SuppressLint
import android.graphics.Typeface
import android.util.Log
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.UIImplementation
Expand All @@ -13,34 +17,42 @@ import okhttp3.internal.wait
internal class ReactTextShadowNodeUtils(
private val reactContext: ReactContext
) {
internal fun getFontSize(shadowNode: ReactTextShadowNode): Int {
shadowNode.javaClass.superclass?.getDeclaredField("mTextAttributes").let {
it?.isAccessible = true
val textAttributes = it?.get(shadowNode) as TextAttributes
return textAttributes.effectiveFontSize
internal fun getFontSize(view: TextView, shadowNode: ReactTextShadowNode?): Long {
shadowNode?.javaClass?.superclass?.getDeclaredField("mTextAttributes")?.let {
it.isAccessible = true
val textAttributes = it.get(shadowNode) as TextAttributes
return textAttributes.effectiveFontSize.toLong()
}

return view.textSize.toLong()
}

internal fun getFontFamily(shadowNode: ReactTextShadowNode): String? {
shadowNode.javaClass.superclass.getDeclaredField("mFontFamily").let {
internal fun getFontFamily(view: TextView, shadowNode: ReactTextShadowNode?): String {
shadowNode?.javaClass?.superclass?.getDeclaredField("mFontFamily")?.let {
it.isAccessible = true
return it.get(shadowNode)?.toString()
?: resolveFontFamily(view.typeface)
}

return resolveFontFamily(view.typeface)
}

internal fun getGravity(viewId: Int): Int? {
val shadowNode = getShadowNode(viewId) as? ReactTextShadowNode ?: return null
shadowNode.javaClass.superclass.getDeclaredField("mTextAlign").let {
internal fun getGravity(view: TextView, shadowNode: ReactTextShadowNode?): Int {
shadowNode?.javaClass?.superclass?.getDeclaredField("mTextAlign")?.let {
it.isAccessible = true
return it.getInt(shadowNode)
}

return view.gravity
}

internal fun getColor(shadowNode: ReactTextShadowNode): Int {
shadowNode.javaClass.superclass.getDeclaredField("mColor").let {
internal fun getColor(view: TextView, shadowNode: ReactTextShadowNode?): Int {
shadowNode?.javaClass?.superclass?.getDeclaredField("mColor")?.let {
it.isAccessible = true
return it.getInt(shadowNode)
}

return view.currentTextColor
}

internal fun getShadowNode(viewId: Int): ReactShadowNode<out ReactShadowNode<*>>? {
Expand Down Expand Up @@ -78,4 +90,23 @@ internal class ReactTextShadowNodeUtils(
return value.resolveShadowNode(tag)
}
}

@SuppressLint("VisibleForTests")
private fun resolveFontFamily(typeface: Typeface?): String =
typefaceMap[typeface]
?: SANS_SERIF_FAMILY_NAME

internal companion object {
@VisibleForTesting
internal const val SANS_SERIF_FAMILY_NAME = "roboto, sans-serif"
private const val SERIF_FAMILY_NAME = "serif"
private const val MONOSPACE_FAMILY_NAME = "monospace"

@VisibleForTesting
internal val typefaceMap: Map<Typeface, String> = mapOf(
Typeface.SANS_SERIF to SANS_SERIF_FAMILY_NAME,
Typeface.MONOSPACE to MONOSPACE_FAMILY_NAME,
Typeface.SERIF to SERIF_FAMILY_NAME
)
}
}
Loading

0 comments on commit 68aa3f2

Please sign in to comment.