Skip to content

Commit

Permalink
Merge "Add method for creating AnnotatedString from HTML tagged strin…
Browse files Browse the repository at this point in the history
…g" into androidx-main
  • Loading branch information
Anastasia Soboleva authored and Gerrit Code Review committed Mar 19, 2024
2 parents 6994324 + 932545d commit a004886
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import androidx.compose.foundation.demos.text2.TextFieldReceiveContentDemo
import androidx.compose.foundation.samples.BasicTextFieldUndoSample
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.integration.demos.common.DemoCategory
import androidx.compose.ui.text.samples.AnnotatedStringFromHtml

val TextDemos = DemoCategory(
"Text",
Expand Down Expand Up @@ -213,5 +214,6 @@ val TextDemos = DemoCategory(
)
),
ComposableDemo("Text Pointer Icon") { TextPointerIconDemo() },
ComposableDemo("Html") { AnnotatedStringFromHtml() }
)
)
4 changes: 4 additions & 0 deletions compose/ui/ui-text/api/current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ package androidx.compose.ui.text {
@SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTextApi {
}

public final class Html_androidKt {
method public static androidx.compose.ui.text.AnnotatedString parseAsHtml(String);
}

@SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This is internal API that may change frequently and without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface InternalTextApi {
}

Expand Down
4 changes: 4 additions & 0 deletions compose/ui/ui-text/api/restricted_current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ package androidx.compose.ui.text {
@SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTextApi {
}

public final class Html_androidKt {
method public static androidx.compose.ui.text.AnnotatedString parseAsHtml(String);
}

@SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This is internal API that may change frequently and without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface InternalTextApi {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.text.samples

import androidx.annotation.Sampled
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.parseAsHtml

@Composable
@Sampled
fun AnnotatedStringFromHtml() {
// First, download a string as a plain text using one of the resources' methods. At this stage
// you will be handling plurals and formatted strings in needed. Moreover, the string will be
// resolved with respect to the current locale and available translations.
val string = stringResource(id = R.string.example)

// Next, convert a string marked with HTML tags into AnnotatedString to be displayed by Text
val styledAnnotatedString = string.parseAsHtml()

BasicText(styledAnnotatedString)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<resources>
<string name="example" translatable="false">
&lt;b>bold&lt;/b>
&lt;i>italic&lt;/i>
&lt;big>big&lt;/big>
&lt;small>small&lt;/small>
&lt;font face="monospace">monospace&lt;/font>
&lt;font face="serif">serif&lt;/font>
&lt;font face="sans_serif">sans_serif&lt;/font>
&lt;font face="cursive">cursive&lt;/font>
&lt;font color="#00ff00">green&lt;/font>
&lt;tt>monospace&lt;/tt>
&lt;sup>superscript&lt;/sup>
&lt;strike>strikethrough&lt;/strike>
&lt;sub>subscript&lt;/sub>
&lt;u>underline&lt;/u>
&lt;span style="background-color:#ff0000">span&lt;/span>
&lt;p dir="rtl">right to left&lt;/p>
&lt;p dir="ltr">left to right&lt;/p>
I am &lt;div>div&lt;/div> element.&lt;br>
&lt;a href="https://developer.android.com">Link&lt;/a>
</string>
</resources>
20 changes: 20 additions & 0 deletions compose/ui/ui-text/src/androidInstrumentedTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="androidx.activity.ComponentActivity" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.text

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.test.junit4.createComposeRule
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.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.em
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class AnnotatedStringFromHtmlTest {
@get:Rule
val rule = createComposeRule()

@Test
// pre-N block-level elements were separated with two new lines
@SdkSuppress(minSdkVersion = 24)
fun buildAnnotatedString_fromHtml() {
rule.setContent {
val expected = buildAnnotatedString {
fun add(block: () -> Unit) {
block()
append("a")
pop()
append(" ")
}
fun addStyle(style: SpanStyle) {
add { pushStyle(style) }
}

add { pushLink(LinkAnnotation.Url("https://example.com")) }
addStyle(SpanStyle(fontWeight = FontWeight.Bold))
addStyle(SpanStyle(fontSize = 1.25.em))
append("\na\n") // <div>
addStyle(SpanStyle(fontFamily = FontFamily.Serif))
addStyle(SpanStyle(color = Color.Green))
addStyle(SpanStyle(fontStyle = FontStyle.Italic))
append("\na\n") // <p>
addStyle(SpanStyle(textDecoration = TextDecoration.LineThrough))
addStyle(SpanStyle(fontSize = 0.8.em))
addStyle(SpanStyle(background = Color.Red))
addStyle(SpanStyle(baselineShift = BaselineShift.Subscript))
addStyle(SpanStyle(baselineShift = BaselineShift.Superscript))
addStyle(SpanStyle(fontFamily = FontFamily.Monospace))
addStyle(SpanStyle(textDecoration = TextDecoration.Underline))
}

val actual = stringResource(androidx.compose.ui.text.test.R.string.html).parseAsHtml()

assertThat(actual.text).isEqualTo(expected.text)
assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles).inOrder()
assertThat(actual.paragraphStyles)
.containsExactlyElementsIn(expected.paragraphStyles)
.inOrder()
assertThat(actual.getStringAnnotations(0, actual.length))
.containsExactlyElementsIn(expected.getStringAnnotations(0, expected.length))
.inOrder()
assertThat(actual.getLinkAnnotations(0, actual.length))
.containsExactlyElementsIn(expected.getLinkAnnotations(0, expected.length))
.inOrder()
}
}

@Test
fun formattedString_withStyling() {
rule.setContent {
val actual = stringResource(
androidx.compose.ui.text.test.R.string.formatting,
"computer"
).parseAsHtml()
assertThat(actual.text).isEqualTo("Hello, computer!")
assertThat(actual.spanStyles).containsExactly(
AnnotatedString.Range(SpanStyle(fontWeight = FontWeight.Bold), 7, 15)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<resources>
<string name="html" translatable="false">
&lt;a href="https://example.com">a&lt;/a>
&lt;b>a&lt;/b>
&lt;big>a&lt;/big>
&lt;div>a&lt;/div>
&lt;font face="serif">a&lt;/font>
&lt;font color="#00ff00">a&lt;/font>
&lt;i>a&lt;/i>
&lt;p>a&lt;/p>
&lt;s>a&lt;/s>
&lt;small>a&lt;/small>
&lt;span style="background-color:red">a&lt;/span>
&lt;sub>a&lt;/sub>
&lt;sup>a&lt;/sup>
&lt;tt>a&lt;/tt>
&lt;u>a&lt;/u>
</string>
<string name="formatting">Hello, &lt;b>%s&lt;/b>!</string>
</resources>
Loading

0 comments on commit a004886

Please sign in to comment.