From c5a464cf4507d8eab5dfda6756c914419bcfa7e8 Mon Sep 17 00:00:00 2001 From: rozPierog <rozpierog@gmail.com> Date: Fri, 26 Apr 2024 10:32:24 +0200 Subject: [PATCH] Fix Annotated link clicks --- .../com/omelan/cofi/components/Description.kt | 12 ---- .../pages/settings/licenses/DependencyItem.kt | 61 +++++++++---------- .../omelan/cofi/utils/AnnotatedStringUtils.kt | 59 ++++-------------- 3 files changed, 40 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/com/omelan/cofi/components/Description.kt b/app/src/main/java/com/omelan/cofi/components/Description.kt index 3dd08c2b..348ca5bc 100644 --- a/app/src/main/java/com/omelan/cofi/components/Description.kt +++ b/app/src/main/java/com/omelan/cofi/components/Description.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextMotion import androidx.compose.ui.tooling.preview.Preview @@ -28,7 +27,6 @@ import com.omelan.cofi.utils.buildAnnotatedStringWithUrls @Composable fun Description(modifier: Modifier = Modifier, descriptionText: String) { - val uriHandler = LocalUriHandler.current var isExpanded by remember { mutableStateOf(false) } var showExpandButton by remember { mutableStateOf(false) } val rotationDegree by animateFloatAsState( @@ -73,16 +71,6 @@ fun Description(modifier: Modifier = Modifier, descriptionText: String) { textMotion = TextMotion.Animated, ), modifier = Modifier.animateContentSize(), -// onClick = { -// descriptionWithLinks -// .getStringAnnotations(URL_ANNOTATION, it, it) -// .firstOrNull()?.let { stringAnnotation -> -// uriHandler.openUri(stringAnnotation.item) -// } -// if (showExpandButton) { -// isExpanded = !isExpanded -// } -// }, onTextLayout = { textLayoutResult -> if (!isExpanded) { showExpandButton = textLayoutResult.didOverflowHeight diff --git a/app/src/main/java/com/omelan/cofi/pages/settings/licenses/DependencyItem.kt b/app/src/main/java/com/omelan/cofi/pages/settings/licenses/DependencyItem.kt index 71575ec2..3adb65f9 100644 --- a/app/src/main/java/com/omelan/cofi/pages/settings/licenses/DependencyItem.kt +++ b/app/src/main/java/com/omelan/cofi/pages/settings/licenses/DependencyItem.kt @@ -3,7 +3,6 @@ package com.omelan.cofi.pages.settings.licenses import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -11,15 +10,16 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withLink import androidx.compose.ui.tooling.preview.Preview import com.omelan.cofi.pages.settings.settingsItemModifier import com.omelan.cofi.share.model.Dependency import com.omelan.cofi.share.model.License import com.omelan.cofi.ui.Spacing -import com.omelan.cofi.utils.URL_ANNOTATION -import com.omelan.cofi.utils.appendLink +import com.omelan.cofi.utils.linkSpanStyle @Composable fun DependencyItem(dependency: Dependency) { @@ -31,11 +31,14 @@ fun DependencyItem(dependency: Dependency) { } val licenses = buildAnnotatedString { dependency.licenses.forEachIndexed { index, license -> - appendLink( - text = license.license, - url = license.license_url, - color = MaterialTheme.colorScheme.secondary, - ) + withLink( + LinkAnnotation.Url( + license.license_url, + style = linkSpanStyle(MaterialTheme.colorScheme.secondary), + ), + ) { + append(license.license) + } if (index != dependency.licenses.size - 1) { append(", ") } @@ -71,16 +74,8 @@ fun DependencyItem(dependency: Dependency) { fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onBackground, ) - ClickableText( + Text( text = licenses, - onClick = { - licenses - .getStringAnnotations(URL_ANNOTATION, it, it) - .firstOrNull()?.let { stringAnnotation -> - uriHandler.openUri(stringAnnotation.item) - return@ClickableText - } - }, modifier = Modifier.padding(bottom = Spacing.normal), ) HorizontalDivider() @@ -94,22 +89,22 @@ fun DependencyPreview() { dependency = Dependency( project = "Nice package", description = "Contains Guava\\u0027s " + - "com.google.common.util.concurrent.ListenableFuture" + - " class,\\n without any of its other classes -- but is also available in " + - "a second\\n \\\"version\\\" that omits the class to avoid conflicts with " + - "the copy in Guava\\n itself. The idea is:\\n\\n - If users want only " + - "ListenableFuture, they depend on listenablefuture-1.0.\\n\\n " + - "- If users want all of Guava, they depend on guava, which, as of Guava\\n " + - " 27.0, depends on\\n " + - " listenablefuture-9999.0-empty-to-avoid-conflict-with-guava. " + - "The 9999.0-...\\n version number is enough for some build systems" + - " (notably, Gradle) to select\\n that empty artifact over the " + - "\\\"real\\\" listenablefuture-1.0 -- avoiding a\\n " + - "conflict with the copy of ListenableFuture in guava itself. If users are\\n " + - " using an older version of Guava or a build system other than Gradle," + - " they\\n may see class conflicts. If so, they can solve them by manually" + - " excluding\\n the listenablefuture artifact or manually forcing their " + - "build systems to\\n use 9999.0-....\",\n", + "com.google.common.util.concurrent.ListenableFuture" + + " class,\\n without any of its other classes -- but is also available in " + + "a second\\n \\\"version\\\" that omits the class to avoid conflicts with " + + "the copy in Guava\\n itself. The idea is:\\n\\n - If users want only " + + "ListenableFuture, they depend on listenablefuture-1.0.\\n\\n " + + "- If users want all of Guava, they depend on guava, which, as of Guava\\n " + + " 27.0, depends on\\n " + + " listenablefuture-9999.0-empty-to-avoid-conflict-with-guava. " + + "The 9999.0-...\\n version number is enough for some build systems" + + " (notably, Gradle) to select\\n that empty artifact over the " + + "\\\"real\\\" listenablefuture-1.0 -- avoiding a\\n " + + "conflict with the copy of ListenableFuture in guava itself. If users are\\n " + + " using an older version of Guava or a build system other than Gradle," + + " they\\n may see class conflicts. If so, they can solve them by manually" + + " excluding\\n the listenablefuture artifact or manually forcing their " + + "build systems to\\n use 9999.0-....\",\n", version = "3.2.1", developers = listOf("Leon Omelan"), url = "jsonObject.getString( url )", diff --git a/app/src/main/java/com/omelan/cofi/utils/AnnotatedStringUtils.kt b/app/src/main/java/com/omelan/cofi/utils/AnnotatedStringUtils.kt index 361dbb2d..a5dc616a 100644 --- a/app/src/main/java/com/omelan/cofi/utils/AnnotatedStringUtils.kt +++ b/app/src/main/java/com/omelan/cofi/utils/AnnotatedStringUtils.kt @@ -4,7 +4,9 @@ import android.annotation.SuppressLint import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.* +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration import java.util.regex.Matcher import java.util.regex.Pattern @@ -16,37 +18,6 @@ fun linkSpanStyle(color: Color) = SpanStyle( textDecoration = TextDecoration.Underline, ) -fun AnnotatedString.Builder.addLinkAnnotation( - start: Int, - text: String, - url: String = text, - color: Color, -) { - addStringAnnotation( - tag = URL_ANNOTATION, - annotation = url, - start = start, - end = start + text.length, - ) - addStyle( - linkSpanStyle(color), - start = start, - end = start + text.length, - ) -} - -@OptIn(ExperimentalTextApi::class) -fun AnnotatedString.Builder.appendLink(text: String, url: String = text, color: Color) { - withStyle(linkSpanStyle(color)) { - withAnnotation( - tag = URL_ANNOTATION, - annotation = url, - ) { - append(text) - } - } -} - private fun extractUrls(text: String): List<String> { val containedUrls: MutableList<String> = arrayListOf() val urlRegex = @@ -67,20 +38,7 @@ private fun extractUrls(text: String): List<String> { @SuppressLint("ComposableNaming") @Composable fun buildAnnotatedStringWithUrls(baseText: String) = - buildAnnotatedString { - val urlsInDescription = extractUrls(baseText) - append(baseText) - var lastPosition = 0 - urlsInDescription.forEach { - val positionOfUrl = baseText.indexOf(it, startIndex = lastPosition) - lastPosition = positionOfUrl - addLinkAnnotation( - start = positionOfUrl, - text = it, - color = MaterialTheme.colorScheme.secondary, - ) - } - } + buildAnnotatedStringWithUrls(baseText, MaterialTheme.colorScheme.secondary) fun buildAnnotatedStringWithUrls(baseText: String, color: Color) = buildAnnotatedString { @@ -90,6 +48,13 @@ fun buildAnnotatedStringWithUrls(baseText: String, color: Color) = urlsInDescription.forEach { val positionOfUrl = baseText.indexOf(it, startIndex = lastPosition) lastPosition = positionOfUrl - addLinkAnnotation(start = positionOfUrl, text = it, color = color) + addLink( + LinkAnnotation.Url( + it, + style = linkSpanStyle(color), + ), + start = positionOfUrl, + end = positionOfUrl + it.length, + ) } }