PM-44005: bug: Add focus state to hypertext links (#7455)

This commit is contained in:
David Perez
2026-10-08 18:53:47 +00:00
committed by GitHub
parent 2b4aaeaa91
commit 1eda72d5f9
3 changed files with 38 additions and 6 deletions
@@ -5,6 +5,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextDecoration
import com.bitwarden.ui.platform.theme.BitwardenTheme
/**
@@ -67,3 +68,17 @@ val bitwardenClickableTextSpanStyle: SpanStyle
color = BitwardenTheme.colorScheme.text.interaction,
textStyle = BitwardenTheme.typography.labelMedium,
)
/**
* A [SpanStyle] representing "clickable" text in the focused state using the background alert
* color and the underlined label medium font.
*/
val bitwardenClickableTextFocusSpanStyle: SpanStyle
@Composable
@ReadOnlyComposable
get() = spanStyleOf(
color = BitwardenTheme.colorScheme.background.alert,
textStyle = BitwardenTheme.typography.labelMedium.copy(
textDecoration = TextDecoration.Underline,
),
)
@@ -26,6 +26,8 @@ import androidx.core.text.getSpans
* with the "emphasis" annotation.
* @param linkHighlightStyle Style to apply to part of the resource that has been annotated with
* the "link" annotation.
* @param linkHighlightFocusStyle Style to apply to part of the resource that has been annotated
* with the "link" annotation and is currently focused.
* @param resources The resources used to access the strings.
* @param onAnnotationClick Callback to invoke when a link annotation is clicked. Will pass back
* the value of the annotation as a string to allow for delineation if there are multiple callbacks
@@ -52,6 +54,7 @@ fun @receiver:StringRes Int.toAnnotatedString(
style: SpanStyle = bitwardenDefaultSpanStyle,
emphasisHighlightStyle: SpanStyle = bitwardenBoldSpanStyle,
linkHighlightStyle: SpanStyle = bitwardenClickableTextSpanStyle,
linkHighlightFocusStyle: SpanStyle = bitwardenClickableTextFocusSpanStyle,
resources: Resources = LocalResources.current,
onAnnotationClick: ((annotationKey: String) -> Unit)? = null,
): AnnotatedString = remember(
@@ -60,6 +63,7 @@ fun @receiver:StringRes Int.toAnnotatedString(
style,
emphasisHighlightStyle,
linkHighlightStyle,
linkHighlightFocusStyle,
onAnnotationClick,
) {
val spannedString = try {
@@ -73,6 +77,7 @@ fun @receiver:StringRes Int.toAnnotatedString(
style = style,
emphasisHighlightStyle = emphasisHighlightStyle,
linkHighlightStyle = linkHighlightStyle,
linkHighlightFocusStyle = linkHighlightFocusStyle,
onAnnotationClick = onAnnotationClick,
)
}
@@ -90,6 +95,8 @@ fun @receiver:StringRes Int.toAnnotatedString(
* with the "emphasis" annotation.
* @param linkHighlightStyle Style to apply to part of the resource that has been annotated with
* the "link" annotation.
* @param linkHighlightFocusStyle Style to apply to part of the resource that has been annotated
* with the "link" annotation and is currently focused.
* @param resources The resources used to access the strings.
* @param onAnnotationClick Callback to invoke when a link annotation is clicked. Will pass back
* the value of the annotation as a string to allow for delineation if there are multiple callbacks
@@ -105,6 +112,7 @@ fun @receiver:PluralsRes Int.toAnnotatedPluralsString(
style: SpanStyle = bitwardenDefaultSpanStyle,
emphasisHighlightStyle: SpanStyle = bitwardenBoldSpanStyle,
linkHighlightStyle: SpanStyle = bitwardenClickableTextSpanStyle,
linkHighlightFocusStyle: SpanStyle = bitwardenClickableTextFocusSpanStyle,
resources: Resources = LocalResources.current,
onAnnotationClick: ((annotationKey: String) -> Unit)? = null,
): AnnotatedString = remember(
@@ -114,6 +122,7 @@ fun @receiver:PluralsRes Int.toAnnotatedPluralsString(
style,
emphasisHighlightStyle,
linkHighlightStyle,
linkHighlightFocusStyle,
onAnnotationClick,
) {
val spannedString = try {
@@ -129,6 +138,7 @@ fun @receiver:PluralsRes Int.toAnnotatedPluralsString(
style = style,
emphasisHighlightStyle = emphasisHighlightStyle,
linkHighlightStyle = linkHighlightStyle,
linkHighlightFocusStyle = linkHighlightFocusStyle,
onAnnotationClick = onAnnotationClick,
)
}
@@ -138,11 +148,13 @@ fun @receiver:PluralsRes Int.toAnnotatedPluralsString(
* `arg` annotations with the corresponding [args] entry then applies emphasis and link styles
* to the resulting [AnnotatedString].
*/
@Suppress("LongParameterList")
private fun SpannedString.buildAnnotatedString(
args: Array<out CharSequence>,
style: SpanStyle,
emphasisHighlightStyle: SpanStyle,
linkHighlightStyle: SpanStyle,
linkHighlightFocusStyle: SpanStyle,
onAnnotationClick: ((annotationKey: String) -> Unit)?,
): AnnotatedString {
// The spannableBuilder is used to help parse through the annotations in the string resource.
@@ -181,6 +193,8 @@ private fun SpannedString.buildAnnotatedString(
tag = annotation.value.orEmpty(),
styles = TextLinkStyles(
style = linkHighlightStyle,
focusedStyle = linkHighlightFocusStyle,
hoveredStyle = linkHighlightFocusStyle,
),
) {
onAnnotationClick?.invoke(annotation.value.orEmpty())
@@ -1,6 +1,7 @@
package com.bitwarden.ui.platform.components.text
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -71,6 +72,7 @@ fun BitwardenClickableText(
modifier = modifier
.defaultMinSize(minHeight = 48.dp)
.clip(shape = shape)
.focusable(enabled = isEnabled)
.clickable(
indication = ripple(color = BitwardenTheme.colorScheme.background.pressed),
interactionSource = interactionSource,
@@ -88,15 +90,16 @@ fun BitwardenClickableText(
contentDescription = formattedAccessibilityString
},
) {
val foregroundColor = if (isEnabled) {
color
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
}
leadingIcon?.let {
Icon(
painter = leadingIcon,
contentDescription = null,
tint = if (isEnabled) {
color
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
tint = foregroundColor,
modifier = Modifier.size(size = 16.dp),
)
Spacer(modifier = Modifier.width(width = 8.dp))
@@ -104,7 +107,7 @@ fun BitwardenClickableText(
Text(
text = label,
textAlign = TextAlign.Start,
color = color,
color = foregroundColor,
style = style,
modifier = Modifier.semantics { hideFromAccessibility() },
)