diff --git a/.idea/codeInsightSettings.xml b/.idea/codeInsightSettings.xml index 4c94a0bbf..37ce37efb 100644 --- a/.idea/codeInsightSettings.xml +++ b/.idea/codeInsightSettings.xml @@ -49,8 +49,8 @@ androidx.compose.material3.ElevatedButton androidx.compose.material3.ElevatedCard androidx.compose.material3.ElevatedFilterChip - androidx.compose.material3.ExtendedFloatingActionButton androidx.compose.material3.ExposedDropdownMenu + androidx.compose.material3.ExtendedFloatingActionButton androidx.compose.material3.FilledIconButton androidx.compose.material3.FilledIconToggleButton androidx.compose.material3.FilledTonalButton diff --git a/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/text/TextLinkExamples.kt b/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/text/TextLinkExamples.kt index 56e60adf0..a697fe348 100644 --- a/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/text/TextLinkExamples.kt +++ b/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/text/TextLinkExamples.kt @@ -24,6 +24,7 @@ package com.adevinta.spark.catalog.examples.samples.text import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.Text import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -38,6 +39,7 @@ import com.adevinta.spark.components.text.TextLinkButton import com.adevinta.spark.icons.Link import com.adevinta.spark.icons.SparkIcons import com.adevinta.spark.res.annotatedStringResource +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.launch private const val TextLinksExampleSourceUrl = "$SampleSourceUrl/TextLinkExamples.kt" @@ -50,28 +52,35 @@ public val TextLinksExamples: List = listOf( sourceUrl = TextLinksExampleSourceUrl, ) { snackbarHostState -> val scope = rememberCoroutineScope() - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxSize(), - ) { - TextLink( - style = SparkTheme.typography.subhead, - text = annotatedStringResource(id = R.string.spark_text_link_short_example_), - lineHeight = 40.sp, - onClickLabel = "Aller au site web", - onClick = { - scope.launch { - snackbarHostState.showSnackbar( - message = "https://kotlinlang.org", - actionLabel = "Action", - duration = SnackbarDuration.Short, - intent = SnackbarIntent.Success, - ) - } - }, - ) - } + TextLink( + style = SparkTheme.typography.subhead, + text = annotatedStringResource(id = R.string.spark_text_link_short_example_), + lineHeight = 40.sp, + onClickLabel = "Aller au site web", + onClick = { + scope.launch { + snackbarHostState.showSnackbar( + message = "https://kotlinlang.org", + actionLabel = "Action", + duration = SnackbarDuration.Short, + intent = SnackbarIntent.Success, + ) + } + }, + ) + Text( + text = annotatedStringResource( + com.adevinta.spark.R.string.spark_annotatedStringResource_test_args, + persistentMapOf("who" to "Bob"), + ), + ) + Text( + text = annotatedStringResource( + com.adevinta.spark.R.string.spark_annotatedStringResource_test_new_args, + "Bob", + ), + ) }, Example( id = "paragraph", diff --git a/catalog/src/main/res/values-fr/strings.xml b/catalog/src/main/res/values-fr/strings.xml index ba5a55a60..17f93e1f2 100644 --- a/catalog/src/main/res/values-fr/strings.xml +++ b/catalog/src/main/res/values-fr/strings.xml @@ -127,9 +127,9 @@ Rechercher une icône Spark - Ceci est la Politique de confidentialitéd’Adevinta ainsi que de nombreuses informations supplémentaires qui pourraient vous intéresser ou dont je devrais vous informer et que vous ignorez peut-être + <annotation color="neutral" typography="body1">Ceci est la </annotation><annotation color="main" typography="body1"><u><b>Politique de confidentialité</b></u></annotation><annotation color="neutral" typography="body1">d’Adevinta ainsi que de nombreuses informations supplémentaires qui pourraient vous intéresser ou dont je devrais vous informer et que vous ignorez peut-être</annotation> - Apprendre la programmation Kotlin https://kotlinlang.org + <annotation color="neutral" typography="display2">Apprendre la programmation Kotlin </annotation><annotation color="success" typography="display3"><u><b>https://kotlinlang.org</b></u></annotation> Le composant Divider/Séparateur fournit une ligne fine et discrète qui sépare et distingue les sections de contenu afin de renforcer la hiérarchie visuelle.\\u2028Voici quelques utilisations courantes:\nSéparer les sections sur une page.\nSéparer les éléments d’une liste.\nCréer un contraste visuel entre les deux côtés d’une page. diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/main/res/values/strings.xml index e61dd07d6..7a7d618ed 100644 --- a/catalog/src/main/res/values/strings.xml +++ b/catalog/src/main/res/values/strings.xml @@ -128,9 +128,9 @@ Search Spark Icon - This is Adevinta Privacy & Policy also lots of extra information you may be interested in or should I inform you about that extra information. you might not be aware of it + <annotation color="neutral" typography="body1">This is Adevinta </annotation><annotation color="main" typography="body1"><u><b>Privacy & Policy</b></u></annotation><annotation color="neutral" typography="body1"> also lots of extra information you may be interested in or should I inform you about that extra information. you might not be aware of it</annotation> - Learn Kotlin Programming https://kotlinlang.org + <annotation color="neutral" typography="display2">Learn Kotlin Programming </annotation><annotation color="success" typography="display3"><u><b>https://kotlinlang.org</b></u></annotation> The Divider component provides a thin, unobtrusive line that separates and distinguishes sections of content to reinforce visual hierarchy.
A few common uses are: diff --git a/spark/src/main/kotlin/com/adevinta/spark/res/AnnotatedStringResource.kt b/spark/src/main/kotlin/com/adevinta/spark/res/AnnotatedStringResource.kt index 1e611b4c9..78daf4e3d 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/res/AnnotatedStringResource.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/res/AnnotatedStringResource.kt @@ -47,12 +47,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.StringAnnotation import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview @@ -114,39 +118,17 @@ public fun annotatedStringResource(@StringRes id: Int, formatArgs: PersistentMap * @param formatArgs the format arguments * @return the [AnnotatedString] data associated with the resource */ -@Deprecated( - message = "Use the annotatedStringResource with PersistentMap overload instead", - replaceWith = ReplaceWith("annotatedStringResource(id, persistentMapOf(formatArgs))"), -) +//@Deprecated( +// message = "Use the annotatedStringResource with PersistentMap overload instead", +// replaceWith = ReplaceWith("annotatedStringResource(id, persistentMapOf(formatArgs))"), +//) @Composable public fun annotatedStringResource(@StringRes id: Int, vararg formatArgs: Any): AnnotatedString { - val resources = resources() val density = LocalDensity.current val colors = SparkTheme.colors val typography = SparkTheme.typography - return remember(id, formatArgs) { - val text = resources.getText(id, *formatArgs) - text.asAnnotatedString(density, colors, typography) - } -} - -private fun Resources.buildSpannedStringWithArgs( - @StringRes id: Int, - args: PersistentMap, -): SpannedString = buildSpannedString { - append(getText(id)) - getSpans().filter { it.key == "variable" } - .forEach { replace(getSpanStart(it), getSpanEnd(it), args.getValue(it.value)) } -} - -private fun Resources.buildSpannedStringWithArgs( - @PluralsRes id: Int, - count: Int, - args: PersistentMap, -): SpannedString = buildSpannedString { - append(getQuantityText(id, count)) - getSpans().filter { it.key == "variable" } - .forEach { replace(getSpanStart(it), getSpanEnd(it), args.getValue(it.value)) } + val string = stringResource(id, *formatArgs) + return AnnotatedString.fromHtml(string).asAnnotatedString(density, colors, typography) } /** @@ -157,15 +139,11 @@ private fun Resources.buildSpannedStringWithArgs( */ @Composable public fun annotatedStringResource(@StringRes id: Int): AnnotatedString { - val resources = resources() val density = LocalDensity.current - val colors = SparkTheme.colors val typography = SparkTheme.typography - return remember(id) { - val text = resources.getText(id) - text.asAnnotatedString(density, colors, typography) - } + val string = stringResource(id) + return AnnotatedString.fromHtml(string).asAnnotatedString(density, colors, typography) } /** @@ -181,14 +159,11 @@ public fun annotatedPluralStringResource( @PluralsRes id: Int, count: Int, ): AnnotatedString { - val resources = resources() val density = LocalDensity.current val colors = SparkTheme.colors val typography = SparkTheme.typography - return remember(id) { - val text = resources.getQuantityText(id, count) - text.asAnnotatedString(density, colors, typography) - } + val string = pluralStringResource(id, count) + return AnnotatedString.fromHtml(string).asAnnotatedString(density, colors, typography) } /** @@ -233,14 +208,30 @@ public fun annotatedPluralStringResource( count: Int, vararg formatArgs: Any, ): AnnotatedString { - val resources = resources() val density = LocalDensity.current val colors = SparkTheme.colors val typography = SparkTheme.typography - return remember(id) { - val text = resources.getQuantityText(id, count, *formatArgs) - text.asAnnotatedString(density, colors, typography) - } + val string = pluralStringResource(id, count, *formatArgs) + return AnnotatedString.fromHtml(string).asAnnotatedString(density, colors, typography) +} + +private fun Resources.buildSpannedStringWithArgs( + @StringRes id: Int, + args: PersistentMap, +): SpannedString = buildSpannedString { + append(getText(id)) + getSpans().filter { it.key == "variable" } + .forEach { replace(getSpanStart(it), getSpanEnd(it), args.getValue(it.value)) } +} + +private fun Resources.buildSpannedStringWithArgs( + @PluralsRes id: Int, + count: Int, + args: PersistentMap, +): SpannedString = buildSpannedString { + append(getQuantityText(id, count)) + getSpans().filter { it.key == "variable" } + .forEach { replace(getSpanStart(it), getSpanEnd(it), args.getValue(it.value)) } } @Composable @@ -284,12 +275,29 @@ private fun Spanned.toHtmlWithoutParagraphs(): String = toHtml() .substringAfter("

") .substringBeforeLast("

") -private fun CharSequence.asAnnotatedString( +@Suppress("UNCHECKED_CAST") // We're sure that we can only get a StringAnnotation +private fun AnnotatedString.asAnnotatedString( + density: Density, + colors: SparkColors, + typography: SparkTypography, +): AnnotatedString { + return mapAnnotations { + when(it.item) { + is StringAnnotation -> SparkStringAnnotations.toStyleAnnotation( + annotation = it as AnnotatedString.Range, + colors = colors, + typography = typography + ) + else -> it + } + } +} + +private fun SpannedString.asAnnotatedString( density: Density, colors: SparkColors, typography: SparkTypography, ): AnnotatedString { - if (this !is Spanned) return AnnotatedString(this.toString()) return buildAnnotatedString { append(this@asAnnotatedString.toString()) getSpans(0, length, Any::class.java).forEach { @@ -326,8 +334,8 @@ private fun AnnotatedString.Builder.buildWithSpan( is SuperscriptSpan -> SpanStyle(baselineShift = BaselineShift.Superscript) is SubscriptSpan -> SpanStyle(baselineShift = BaselineShift.Subscript) is ForegroundColorSpan -> SpanStyle(color = Color(it.foregroundColor)) - is Annotation -> SparkStringAnnotations.toSpanStyle(annotation = it, colors, typography) ?: return - else -> return +// is Annotation -> SparkStringAnnotations.toSpanStyle(annotation = it, colors, typography) ?: return + else -> return/**/ } addStyle(span, start, end) } @@ -368,5 +376,11 @@ private fun AnnotatedStringResourcePreview() { persistentMapOf("who" to "Bob"), ), ) + Text( + text = annotatedStringResource( + R.string.spark_annotatedStringResource_test_new_args, + "Bob", + ), + ) } } diff --git a/spark/src/main/kotlin/com/adevinta/spark/res/SparkStringAnnotations.kt b/spark/src/main/kotlin/com/adevinta/spark/res/SparkStringAnnotations.kt index 3dfeb3ff9..03b3f89e1 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/res/SparkStringAnnotations.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/res/SparkStringAnnotations.kt @@ -21,9 +21,13 @@ */ package com.adevinta.spark.res -import android.text.Annotation import android.util.Log +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString.Annotation +import androidx.compose.ui.text.AnnotatedString.Range import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.StringAnnotation +import androidx.compose.ui.unit.TextUnit import com.adevinta.spark.tokens.SparkColors import com.adevinta.spark.tokens.SparkTypography @@ -35,53 +39,89 @@ public object SparkStringAnnotations { /** * Given a string representing an annotation key and a string representing an annotation value, returns the corresponding [SpanStyle]. */ - public fun toSpanStyle( - annotation: Annotation, + public fun toStyleAnnotation( + annotation: Range, colors: SparkColors, typography: SparkTypography, - ): SpanStyle? = when (annotation.key) { - "color" -> annotation.value.toColorSpanStyle(colors) - "typography" -> annotation.value.toTypographySpanStyle(typography) - else -> null.also { _ -> - Log.d("StringResources", "Annotation $this is not supported by spark") + ): Range = when (annotation.tag) { + "color" -> annotation.toColorSpanStyle(colors) + "typography" -> annotation.toTypographySpanStyle(typography) + "variable" -> annotation.toTypographySpanStyle(typography) + else -> annotation.also { _ -> + Log.d("StringResources", "Annotation ${annotation.tag} is not supported by spark") } } /** * Given a string representing annotation value of a spark color, returns the corresponding [SpanStyle] with the color token. */ - private fun String.toColorSpanStyle(token: SparkColors): SpanStyle? = when (this) { - "main" -> token.main - "support" -> token.support - "success" -> token.success - "alert" -> token.alert - "error" -> token.error - "info" -> token.info - "neutral" -> token.neutral - "accent" -> token.accent - else -> null.also { _ -> - Log.d("StringResources", "Spark color annotation : $this is not supported") + private fun Range.toColorSpanStyle(token: SparkColors): Range { + val color = when (this.item.value) { + "main" -> token.main + "support" -> token.support + "success" -> token.success + "alert" -> token.alert + "error" -> token.error + "info" -> token.info + "neutral" -> token.neutral + "accent" -> token.accent + else -> null.also { _ -> + Log.d("StringResources", "Spark color annotation : $this is not supported") + } } - }?.let(::SpanStyle) + return Range( + item = SpanStyle( + color = color ?: Color.Unspecified, + ), + start = start, + end = end, + tag = tag, + ) + } /** * Given a string representing annotation value of a spark typography, returns the corresponding [SpanStyle] with the typography token. */ - private fun String.toTypographySpanStyle(token: SparkTypography): SpanStyle? = when (this) { - "display1" -> token.display1 - "display2" -> token.display2 - "display3" -> token.display3 - "headline1" -> token.headline1 - "headline2" -> token.headline2 - "subhead" -> token.subhead - "large" -> token.body1 - "body1" -> token.body1 - "body2" -> token.body2 - "caption" -> token.caption - "small" -> token.small - "callout" -> token.callout - else -> null.also { _ -> - Log.d("StringResources", "Spark typography annotation : $this is not supported") + private fun Range.toTypographySpanStyle(token: SparkTypography): Range { + val typography = when (this.item.value) { + "display1" -> token.display1 + "display2" -> token.display2 + "display3" -> token.display3 + "headline1" -> token.headline1 + "headline2" -> token.headline2 + "subhead" -> token.subhead + "large" -> token.body1 + "body1" -> token.body1 + "body2" -> token.body2 + "caption" -> token.caption + "small" -> token.small + "callout" -> token.callout + else -> null.also { _ -> + Log.d("StringResources", "Spark typography annotation : $this is not supported") + } } - }?.toSpanStyle() + return Range( + item = SpanStyle( + color = Color.Unspecified, + fontSize = typography?.fontSize ?: TextUnit.Unspecified, + fontWeight = typography?.fontWeight, + fontStyle = typography?.fontStyle, + fontSynthesis = typography?.fontSynthesis, + fontFamily = typography?.fontFamily, + fontFeatureSettings = typography?.fontFeatureSettings, + letterSpacing = typography?.letterSpacing ?: TextUnit.Unspecified, + baselineShift = typography?.baselineShift, + textGeometricTransform = typography?.textGeometricTransform, + localeList = typography?.localeList, + background = typography?.background ?: Color.Unspecified, + textDecoration = typography?.textDecoration, + shadow = typography?.shadow, + platformStyle = typography?.platformStyle?.spanStyle, + drawStyle = typography?.drawStyle, + ), + start = start, + end = end, + tag = tag, + ) + } } diff --git a/spark/src/main/res/values-fr/strings.xml b/spark/src/main/res/values-fr/strings.xml index 8f92c6461..761282fbb 100644 --- a/spark/src/main/res/values-fr/strings.xml +++ b/spark/src/main/res/values-fr/strings.xml @@ -60,8 +60,9 @@ Boîte de dialogue - Les Meilleures pratiques pour les textes sur Android + Les <b>Meilleures</b> <annotation typography="large">pratiques</annotation> pour les textes sur <i><annotation color="main">Android</annotation></i> Salut, who! + Salut, <b><annotation typography="large" color="main">%1$s</annotation></b>! Champs requis diff --git a/spark/src/main/res/values/strings.xml b/spark/src/main/res/values/strings.xml index 868fa9030..5e6140d2d 100644 --- a/spark/src/main/res/values/strings.xml +++ b/spark/src/main/res/values/strings.xml @@ -56,8 +56,9 @@ Dialog - Best practices

for text on

Android
+ <b>Best</b> <annotation typography="large">practices</annotation> <p dir="rtl">for text on</p> <i><annotation color="main">Android</annotation></i> Hello, who! + Hello, <b><annotation color="main" typography="large">%1$s</annotation></b>! Mandatory