Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8071dcee authored by Steven Lee's avatar Steven Lee
Browse files

Make TogglePermissionAppListModel, AppInfoPage and Footer could support using...

Make TogglePermissionAppListModel, AppInfoPage and Footer could support using AnnotatedString for the permission description.

  - Adds AnnotatedStringResource helper methods to help convert spannable strings from xml file to AnnotatedString.
    - Currently only supports StyleSpan and URLSpan.
  - In Footer: adds AnnotatedString support and uses ClickableText to support URLSpan click handling.
  - In AppInfoPage: prioritize Footer with AnnotatedString usage when provided footerAnnotatedString is not empty.
  - In TogglePermissionAppInfoPage: adds footerAnnotatedString parameter passing.
  - In TogglePermissionAppListModel: adds @Composable footerAnnotatedString() method with default empty AnnotatedString return.

Legal tracker bug: 262937731
Privacy tracker bug: 262936913
UX tracker bug: 262938050

Bug: 259436697
Bug: 263163538
Test: SettingsLib/Settings/SettingsGoogle builds.
Test: atest SpaLibTests:com.android.settingslib.spa.framework.util.AnnotatedStringResourceTest
Change-Id: I3cc7dfde298f78f435703c266a1f09e9dc31cff7
parent f864b5a8
Loading
Loading
Loading
Loading
+120 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.settingslib.spa.framework.util

import android.content.res.Resources
import android.graphics.Typeface
import android.text.Spanned
import android.text.style.StyleSpan
import android.text.style.URLSpan
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Density

const val URLSPAN_TAG = "URLSPAN_TAG"

@Composable
fun annotatedStringResource(@StringRes id: Int, urlSpanColor: Color): AnnotatedString {
    LocalConfiguration.current
    val resources = LocalContext.current.resources
    val density = LocalDensity.current
    return remember(id) {
        val text = resources.getText(id)
        spannableStringToAnnotatedString(text, density, urlSpanColor)
    }
}

private fun spannableStringToAnnotatedString(text: CharSequence, density: Density, urlSpanColor: Color): AnnotatedString {
    return if (text is Spanned) {
        with(density) {
            buildAnnotatedString {
                append((text.toString()))
                text.getSpans(0, text.length, Any::class.java).forEach {
                    val start = text.getSpanStart(it)
                    val end = text.getSpanEnd(it)
                    when (it) {
                        is StyleSpan ->
                            when (it.style) {
                                Typeface.NORMAL -> addStyle(
                                        SpanStyle(
                                                fontWeight = FontWeight.Normal,
                                                fontStyle = FontStyle.Normal
                                        ),
                                        start,
                                        end
                                )
                                Typeface.BOLD -> addStyle(
                                        SpanStyle(
                                                fontWeight = FontWeight.Bold,
                                                fontStyle = FontStyle.Normal
                                        ),
                                        start,
                                        end
                                )
                                Typeface.ITALIC -> addStyle(
                                        SpanStyle(
                                                fontWeight = FontWeight.Normal,
                                                fontStyle = FontStyle.Italic
                                        ),
                                        start,
                                        end
                                )
                                Typeface.BOLD_ITALIC -> addStyle(
                                        SpanStyle(
                                                fontWeight = FontWeight.Bold,
                                                fontStyle = FontStyle.Italic
                                        ),
                                        start,
                                        end
                                )
                            }
                        is URLSpan -> {
                            addStyle(
                                    SpanStyle(
                                            color = urlSpanColor,
                                    ),
                                    start,
                                    end
                            )
                            if (!it.url.isNullOrEmpty()) {
                                addStringAnnotation(
                                        URLSPAN_TAG,
                                        it.url,
                                        start,
                                        end
                                )
                            }
                        }
                        else -> addStyle(SpanStyle(), start, end)
                    }
                }
            }
        }
    } else {
        AnnotatedString(text.toString())
    }
}
+12 −5
Original line number Diff line number Diff line
@@ -34,6 +34,13 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
fun Footer(footerText: String) {
    if (footerText.isEmpty()) return
    Footer {
        SettingsBody(footerText)
    }
}

@Composable
fun Footer(content: @Composable () -> Unit) {
    Column(Modifier.padding(SettingsDimension.itemPadding)) {
        Icon(
                imageVector = Icons.Outlined.Info,
@@ -42,7 +49,7 @@ fun Footer(footerText: String) {
                tint = MaterialTheme.colorScheme.onSurfaceVariant,
        )
        Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
        SettingsBody(footerText)
        content()
    }
}

+2 −0
Original line number Diff line number Diff line
@@ -25,4 +25,6 @@
        =1    {There is one song found in {place}.}
        other {There are # songs found in {place}.}
    }</string>

    <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.google.com/">link</a>.</string>
</resources>
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.settingslib.spa.framework.util

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.util.URLSPAN_TAG
import com.android.settingslib.spa.framework.util.annotatedStringResource
import com.android.settingslib.spa.test.R
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AnnotatedStringResourceTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun testAnnotatedStringResource() {
        composeTestRule.setContent {
            val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource, Color.Blue)

            val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
            assertThat(annotations).hasSize(1)
            assertThat(annotations[0].start).isEqualTo(31)
            assertThat(annotations[0].end).isEqualTo(35)
            assertThat(annotations[0].tag).isEqualTo(URLSPAN_TAG)
            assertThat(annotations[0].item).isEqualTo("https://www.google.com/")

            assertThat(annotatedString.spanStyles).hasSize(2)
            assertThat(annotatedString.spanStyles[0].start).isEqualTo(22)
            assertThat(annotatedString.spanStyles[0].end).isEqualTo(26)
            assertThat(annotatedString.spanStyles[0].item).isEqualTo(
                    SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal))

            assertThat(annotatedString.spanStyles[1].start).isEqualTo(31)
            assertThat(annotatedString.spanStyles[1].end).isEqualTo(35)
            assertThat(annotatedString.spanStyles[1].item).isEqualTo(SpanStyle(color = Color.Blue))
        }
    }
}
+6 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ fun AppInfoPage(
    packageName: String,
    userId: Int,
    footerText: String,
    footerContent: (@Composable () -> Unit)?,
    packageManagers: IPackageManagers,
    content: @Composable PackageInfo.() -> Unit,
) {
@@ -40,6 +41,10 @@ fun AppInfoPage(

        packageInfo.content()

        if (footerContent != null) {
            Footer(footerContent)
        } else {
            Footer(footerText)
        }
    }
}
Loading