Bug 1824134 - Add support for clickable linkified text in OnboardingPage.kt

fenix/113.0
rahulsainani 1 year ago committed by mergify[bot]
parent 0f93e5a97d
commit 7a362e76ea

@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
@ -24,6 +25,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R
@ -38,18 +42,24 @@ import org.mozilla.fenix.theme.FirefoxTheme
*/
private const val IMAGE_HEIGHT_RATIO = 0.4f
/**
* The tag used for links in the text for annotated strings.
*/
private const val URL_TAG = "URL_TAG"
/**
* A composable for displaying onboarding page content.
*
* @param pageState [OnboardingPageState] The page content that's displayed.
* @param onDismiss Invoked when the user clicks the close button.
* @param modifier The modifier to be applied to the Composable.
* @param onDismiss Invoked when the user clicks the close button. This defaults to null. When null,
* it doesn't show the close button.
*/
@Composable
fun OnboardingPage(
pageState: OnboardingPageState,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null,
) {
BoxWithConstraints(
modifier = Modifier
@ -65,15 +75,19 @@ fun OnboardingPage(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween,
) {
IconButton(
onClick = onDismiss,
modifier = Modifier.align(Alignment.End),
) {
Icon(
painter = painterResource(id = R.drawable.mozac_ic_close),
contentDescription = stringResource(R.string.onboarding_home_content_description_close_button),
tint = FirefoxTheme.colors.iconPrimary,
)
if (onDismiss != null) {
IconButton(
onClick = onDismiss,
modifier = Modifier.align(Alignment.End),
) {
Icon(
painter = painterResource(id = R.drawable.mozac_ic_close),
contentDescription = stringResource(R.string.onboarding_home_content_description_close_button),
tint = FirefoxTheme.colors.iconPrimary,
)
}
} else {
Spacer(Modifier)
}
Column(
@ -98,11 +112,9 @@ fun OnboardingPage(
Spacer(modifier = Modifier.height(16.dp))
Text(
text = pageState.description,
color = FirefoxTheme.colors.textSecondary,
textAlign = TextAlign.Center,
style = FirefoxTheme.typography.body2,
DescriptionText(
description = pageState.description,
linkTextState = pageState.linkTextState,
)
}
@ -131,6 +143,71 @@ fun OnboardingPage(
}
}
@Composable
private fun DescriptionText(
description: String,
linkTextState: LinkTextState?,
) {
if (linkTextState != null) {
LinkText(
text = description,
linkTextState = linkTextState,
)
} else {
Text(
text = description,
color = FirefoxTheme.colors.textSecondary,
textAlign = TextAlign.Center,
style = FirefoxTheme.typography.body2,
)
}
}
/**
* A composable for displaying text that contains a clickable link text.
*
* @param text The complete text.
* @param linkTextState The clickable part of the text.
*/
@Composable
private fun LinkText(
text: String,
linkTextState: LinkTextState,
) {
val annotatedString = buildAnnotatedString {
val startIndex = text.indexOf(linkTextState.text)
val endIndex = startIndex + linkTextState.text.length
append(text)
addStyle(
style = SpanStyle(color = FirefoxTheme.colors.textAccent),
start = startIndex,
end = endIndex,
)
addStringAnnotation(
tag = URL_TAG,
annotation = linkTextState.url,
start = startIndex,
end = endIndex,
)
}
ClickableText(
text = annotatedString,
style = FirefoxTheme.typography.body2.copy(
textAlign = TextAlign.Center,
color = FirefoxTheme.colors.textSecondary,
),
onClick = {
val range: AnnotatedString.Range<String>? =
annotatedString.getStringAnnotations(URL_TAG, it, it).firstOrNull()
range?.let { stringAnnotation ->
linkTextState.onClick(stringAnnotation.item)
}
},
)
}
@LightDarkPreview
@Composable
private fun OnboardingPagePreview() {

@ -12,6 +12,7 @@ import androidx.annotation.DrawableRes
* @param image [DrawableRes] displayed on the page.
* @param title [String] title of the page.
* @param description [String] description of the page.
* @param linkTextState [LinkTextState] part of description text with a link.
* @param primaryButton [Action] action for the primary button.
* @param secondaryButton [Action] action for the secondary button.
* @param onRecordImpressionEvent Callback for recording impression event.
@ -20,11 +21,21 @@ data class OnboardingPageState(
@DrawableRes val image: Int,
val title: String,
val description: String,
val linkTextState: LinkTextState? = null,
val primaryButton: Action,
val secondaryButton: Action? = null,
val onRecordImpressionEvent: () -> Unit,
)
/**
* Model containing link text, url and action.
*/
data class LinkTextState(
val text: String,
val url: String,
val onClick: (String) -> Unit,
)
/**
* Model containing text and action for a button.
*/

Loading…
Cancel
Save