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

[SR] Detect dominant color for TextViews with Spans #3682

Merged
merged 8 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- Avoid stopping appStartProfiler after application creation ([#3630](https://github.com/getsentry/sentry-java/pull/3630))
- Session Replay: Correctly detect dominant color for `TextView`s with Spans ([#3682](https://github.com/getsentry/sentry-java/pull/3682))

*Breaking changes*:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import android.graphics.drawable.VectorDrawable
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.text.Layout
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.View
import android.widget.TextView
import java.lang.NullPointerException
Expand Down Expand Up @@ -101,3 +103,27 @@ internal val TextView.totalPaddingTopSafe: Int
} catch (e: NullPointerException) {
extendedPaddingTop
}

internal val TextView.dominantTextColor: Int get() {
if (text !is Spanned) return currentTextColor

val spans = (text as Spanned).getSpans(0, text.length, ForegroundColorSpan::class.java)

// determine the dominant color by the span with the longest range
var longestSpan = Int.MIN_VALUE
var dominantColor: Int? = null
for (span in spans) {
val spanStart = (text as Spanned).getSpanStart(span)
val spanEnd = (text as Spanned).getSpanEnd(span)
if (spanStart == -1 || spanEnd == -1) {
// the span is not attached
continue
}
val spanLength = spanEnd - spanStart
if (spanLength > longestSpan) {
longestSpan = spanLength
dominantColor = span.foregroundColor
}
}
return dominantColor ?: currentTextColor
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.TextView
import io.sentry.SentryOptions
import io.sentry.android.replay.util.dominantTextColor
import io.sentry.android.replay.util.isRedactable
import io.sentry.android.replay.util.isVisibleToUser
import io.sentry.android.replay.util.totalPaddingTopSafe
Expand Down Expand Up @@ -244,7 +245,7 @@ sealed class ViewHierarchyNode(
parent.setImportantForCaptureToAncestors(true)
return TextViewHierarchyNode(
layout = view.layout,
dominantColor = view.currentTextColor.toOpaque(),
dominantColor = view.dominantTextColor.toOpaque(),
paddingLeft = view.totalPaddingLeft,
paddingTop = view.totalPaddingTopSafe,
x = view.x,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.sentry.android.replay.util

import android.graphics.Color
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.widget.TextView
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals

@RunWith(AndroidJUnit4::class)
@Config(sdk = [30])
class TextViewDominantColorTest {

@Test
fun `when no spans, returns currentTextColor`() {
val textView = TextView(ApplicationProvider.getApplicationContext())
textView.text = "Hello, World!"
textView.setTextColor(Color.WHITE)

assertEquals(Color.WHITE, textView.dominantTextColor)
}

@Test
fun `when has a foreground color span, returns its color`() {
val textView = TextView(ApplicationProvider.getApplicationContext())
val text = "Hello, World!"
textView.text = SpannableString(text).apply {
setSpan(ForegroundColorSpan(Color.RED), 0, text.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
textView.setTextColor(Color.WHITE)

assertEquals(Color.RED, textView.dominantTextColor)
}

@Test
fun `when has multiple foreground color spans, returns color of the longest span`() {
val textView = TextView(ApplicationProvider.getApplicationContext())
val text = "Hello, World!"
textView.text = SpannableString(text).apply {
setSpan(ForegroundColorSpan(Color.RED), 0, 5, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
setSpan(ForegroundColorSpan(Color.BLACK), 6, text.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
textView.setTextColor(Color.WHITE)

assertEquals(Color.BLACK, textView.dominantTextColor)
}
}
Loading