From 53950365217d2fbdf966194988bdd9da36406b89 Mon Sep 17 00:00:00 2001 From: Alexandru2909 Date: Mon, 14 Aug 2023 13:12:47 +0300 Subject: [PATCH] Bug 1840110 - Move LinkText to compose package --- .../fenix/compose/ClickableSubstringLink.kt | 2 + .../org/mozilla/fenix/compose/LinkText.kt | 129 ++++++++++++++++++ .../home/pocket/PocketStoriesComposables.kt | 1 + .../onboarding/view/JunoOnboardingMapper.kt | 1 + .../fenix/onboarding/view/OnboardingPage.kt | 73 +--------- .../onboarding/view/OnboardingPageState.kt | 10 +- .../settings/wallpaper/WallpaperSettings.kt | 1 + .../fenix/shopping/ui/ProductAnalysis.kt | 1 + .../view/JunoOnboardingMapperTest.kt | 1 + 9 files changed, 139 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/compose/LinkText.kt diff --git a/app/src/main/java/org/mozilla/fenix/compose/ClickableSubstringLink.kt b/app/src/main/java/org/mozilla/fenix/compose/ClickableSubstringLink.kt index 12e0612764..89bb677825 100644 --- a/app/src/main/java/org/mozilla/fenix/compose/ClickableSubstringLink.kt +++ b/app/src/main/java/org/mozilla/fenix/compose/ClickableSubstringLink.kt @@ -29,6 +29,7 @@ import org.mozilla.fenix.theme.FirefoxTheme * @param clickableEndIndex [text] index at which the URL substring ends. * @param onClick Callback to be invoked only when the URL substring is clicked. */ +@Deprecated("Use LinkText instead", ReplaceWith("LinkText", "org.mozilla.fenix.compose.LinkText")) @Composable fun ClickableSubstringLink( text: String, @@ -86,6 +87,7 @@ fun ClickableSubstringLink( } @Composable +@Suppress("Deprecation") @Preview private fun ClickableSubstringTextPreview() { val text = "This text contains a link" diff --git a/app/src/main/java/org/mozilla/fenix/compose/LinkText.kt b/app/src/main/java/org/mozilla/fenix/compose/LinkText.kt new file mode 100644 index 0000000000..7ab18679bc --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/compose/LinkText.kt @@ -0,0 +1,129 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.text.ClickableText +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role +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.tooling.preview.Preview +import mozilla.components.support.ktx.android.content.isScreenReaderEnabled +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * The tag used for links in the text for annotated strings. + */ +private const val URL_TAG = "URL_TAG" + +/** + * Model containing link text, url and action. + * + * @property text Substring of the text passed to [LinkText] to be displayed as clickable link. + * @property url Url the link should point to. + * @property onClick Callback to be invoked when link is clicked. + */ +data class LinkTextState( + val text: String, + val url: String, + val onClick: (String) -> Unit, +) + +/** + * 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 +fun LinkText( + text: String, + linkTextState: LinkTextState, +) { + val context = LocalContext.current + val annotatedString = buildAnnotatedString { + val startIndex = text.indexOf(linkTextState.text, ignoreCase = true) + 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, + ) + } + + // When using UrlAnnotation, talkback shows links in a separate dialog and + // opens them in the default browser. Since this component allows the caller to define the + // onClick behaviour - e.g. to open the link in in-app custom tab, here StringAnnotation is used + // and modifier is enabled with Role.Button when screen reader is enabled. + ClickableText( + text = annotatedString, + style = FirefoxTheme.typography.body2.copy( + textAlign = TextAlign.Center, + color = FirefoxTheme.colors.textSecondary, + ), + modifier = Modifier.clickable( + enabled = context.isScreenReaderEnabled, + role = Role.Button, + onClickLabel = linkTextState.text, + onClick = { linkTextState.onClick(linkTextState.url) }, + ), + onClick = { + if (!context.isScreenReaderEnabled) { + val range: AnnotatedString.Range? = + annotatedString.getStringAnnotations(URL_TAG, it, it).firstOrNull() + range?.let { stringAnnotation -> + linkTextState.onClick(stringAnnotation.item) + } + } + }, + ) +} + +@Preview +@Composable +private fun LinkTextEndPreview() { + val state = LinkTextState( + text = "click here", + url = "www.mozilla.com", + onClick = {}, + ) + FirefoxTheme { + Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) { + LinkText(text = "This is normal text, click here", linkTextState = state) + } + } +} + +@Preview +@Composable +private fun LinkTextMiddlePreview() { + val state = LinkTextState( + text = "clickable text", + url = "www.mozilla.com", + onClick = {}, + ) + FirefoxTheme { + Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) { + LinkText(text = "This is clickable text, followed by normal text", linkTextState = state) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesComposables.kt b/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesComposables.kt index 3994f7d2f0..39aef4981a 100644 --- a/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesComposables.kt +++ b/app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesComposables.kt @@ -507,6 +507,7 @@ fun PocketStoriesCategories( * @param linkTextColor [Color] of the link text. */ @OptIn(ExperimentalComposeUiApi::class) +@Suppress("Deprecation") @Composable fun PoweredByPocketHeader( onLearnMoreClicked: (String) -> Unit, diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapper.kt b/app/src/main/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapper.kt index dcb1d6db3f..393318f47c 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapper.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapper.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.onboarding.view import androidx.compose.ui.layout.ContentScale +import org.mozilla.fenix.compose.LinkTextState import org.mozilla.fenix.nimbus.OnboardingCardData import org.mozilla.fenix.nimbus.OnboardingCardType import org.mozilla.fenix.settings.SupportUtils diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPage.kt b/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPage.kt index 9593e99ff0..9f6065c1d6 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPage.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPage.kt @@ -6,7 +6,6 @@ package org.mozilla.fenix.onboarding.view import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraintsScope @@ -16,7 +15,6 @@ 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 @@ -28,18 +26,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -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 androidx.compose.ui.unit.dp -import mozilla.components.support.ktx.android.content.isScreenReaderEnabled import org.mozilla.fenix.R +import org.mozilla.fenix.compose.LinkText +import org.mozilla.fenix.compose.LinkTextState import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.compose.button.PrimaryButton import org.mozilla.fenix.compose.button.SecondaryButton @@ -62,11 +56,6 @@ private const val IMAGE_HEIGHT_RATIO_MEDIUM = 0.36f */ private const val IMAGE_HEIGHT_RATIO_SMALL = 0.28f -/** - * 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. * @@ -190,64 +179,6 @@ private fun DescriptionText( } } -/** - * 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 context = LocalContext.current - val annotatedString = buildAnnotatedString { - val startIndex = text.indexOf(linkTextState.text, ignoreCase = true) - 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, - ) - } - - // When using UrlAnnotation, talkback shows links in a separate dialog and - // opens them in the default browser. Since this component allows the caller to define the - // onClick behaviour - e.g. to open the link in in-app custom tab, here StringAnnotation is used - // and modifier is enabled with Role.Button when screen reader is enabled. - ClickableText( - text = annotatedString, - style = FirefoxTheme.typography.body2.copy( - textAlign = TextAlign.Center, - color = FirefoxTheme.colors.textSecondary, - ), - modifier = Modifier.clickable( - enabled = context.isScreenReaderEnabled, - role = Role.Button, - onClickLabel = linkTextState.text, - onClick = { linkTextState.onClick(linkTextState.url) }, - ), - onClick = { - if (!context.isScreenReaderEnabled) { - val range: AnnotatedString.Range? = - annotatedString.getStringAnnotations(URL_TAG, it, it).firstOrNull() - range?.let { stringAnnotation -> - linkTextState.onClick(stringAnnotation.item) - } - } - }, - ) -} - /** * Calculates the image height to be set. The ratio is selected based on parent height. */ diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPageState.kt b/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPageState.kt index 39138af1a8..befd3febce 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPageState.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/view/OnboardingPageState.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.onboarding.view import androidx.annotation.DrawableRes +import org.mozilla.fenix.compose.LinkTextState /** * Model containing data for [OnboardingPage]. @@ -27,15 +28,6 @@ data class OnboardingPageState( 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. */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt index 6f243db4e7..d8be72de69 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt @@ -113,6 +113,7 @@ fun WallpaperSettings( } @Composable +@Suppress("Deprecation") private fun WallpaperGroupHeading( collection: Wallpaper.Collection, onLearnMoreClick: (String, String) -> Unit, diff --git a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt index 42b4880025..4f7668d938 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt @@ -320,6 +320,7 @@ private fun SettingsCard( } } +@Suppress("Deprecation") @Composable private fun ReviewQualityInfo( modifier: Modifier = Modifier, diff --git a/app/src/test/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapperTest.kt b/app/src/test/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapperTest.kt index bf0cc11a03..30fedd734a 100644 --- a/app/src/test/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapperTest.kt +++ b/app/src/test/java/org/mozilla/fenix/onboarding/view/JunoOnboardingMapperTest.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.layout.ContentScale import org.junit.Assert.assertEquals import org.junit.Test import org.mozilla.fenix.R +import org.mozilla.fenix.compose.LinkTextState import org.mozilla.fenix.settings.SupportUtils class JunoOnboardingMapperTest {