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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
@ -24,6 +25,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource 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.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -38,18 +42,24 @@ import org.mozilla.fenix.theme.FirefoxTheme
*/ */
private const val IMAGE_HEIGHT_RATIO = 0.4f 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. * A composable for displaying onboarding page content.
* *
* @param pageState [OnboardingPageState] The page content that's displayed. * @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 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 @Composable
fun OnboardingPage( fun OnboardingPage(
pageState: OnboardingPageState, pageState: OnboardingPageState,
onDismiss: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onDismiss: (() -> Unit)? = null,
) { ) {
BoxWithConstraints( BoxWithConstraints(
modifier = Modifier modifier = Modifier
@ -65,15 +75,19 @@ fun OnboardingPage(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween, verticalArrangement = Arrangement.SpaceBetween,
) { ) {
IconButton( if (onDismiss != null) {
onClick = onDismiss, IconButton(
modifier = Modifier.align(Alignment.End), onClick = onDismiss,
) { modifier = Modifier.align(Alignment.End),
Icon( ) {
painter = painterResource(id = R.drawable.mozac_ic_close), Icon(
contentDescription = stringResource(R.string.onboarding_home_content_description_close_button), painter = painterResource(id = R.drawable.mozac_ic_close),
tint = FirefoxTheme.colors.iconPrimary, contentDescription = stringResource(R.string.onboarding_home_content_description_close_button),
) tint = FirefoxTheme.colors.iconPrimary,
)
}
} else {
Spacer(Modifier)
} }
Column( Column(
@ -98,11 +112,9 @@ fun OnboardingPage(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( DescriptionText(
text = pageState.description, description = pageState.description,
color = FirefoxTheme.colors.textSecondary, linkTextState = pageState.linkTextState,
textAlign = TextAlign.Center,
style = FirefoxTheme.typography.body2,
) )
} }
@ -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 @LightDarkPreview
@Composable @Composable
private fun OnboardingPagePreview() { private fun OnboardingPagePreview() {

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

Loading…
Cancel
Save