1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.permissioncontroller.wear.permission.components
17
18 import android.text.Spanned
19 import android.text.style.ClickableSpan
20 import android.view.View
21 import androidx.compose.foundation.text.BasicText
22 import androidx.compose.runtime.Composable
23 import androidx.compose.ui.Modifier
24 import androidx.compose.ui.graphics.Color
25 import androidx.compose.ui.platform.LocalContext
26 import androidx.compose.ui.text.AnnotatedString
27 import androidx.compose.ui.text.LinkAnnotation
28 import androidx.compose.ui.text.LinkInteractionListener
29 import androidx.compose.ui.text.SpanStyle
30 import androidx.compose.ui.text.TextStyle
31 import androidx.compose.ui.text.buildAnnotatedString
32 import androidx.compose.ui.text.style.TextDecoration
33 import androidx.wear.compose.material.MaterialTheme
34 import java.util.Locale
35
36 const val CLICKABLE_SPAN_TAG = "CLICKABLE_SPAN_TAG"
37
<lambda>null38 fun String.capitalize(): String = replaceFirstChar {
39 if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
40 }
41
42 @Composable
AnnotatedTextnull43 fun AnnotatedText(
44 text: CharSequence,
45 style: TextStyle,
46 modifier: Modifier = Modifier,
47 shouldCapitalize: Boolean,
48 ) {
49 val onClickCallbacks = mutableMapOf<String, (View) -> Unit>()
50 val context = LocalContext.current
51 val listener = LinkInteractionListener {
52 if (it is LinkAnnotation.Clickable) {
53 onClickCallbacks[it.tag]?.invoke(View(context))
54 }
55 }
56
57 val annotatedString =
58 spannableStringToAnnotatedString(
59 text,
60 shouldCapitalize,
61 onClickCallbacks,
62 listener = listener,
63 )
64 BasicText(text = annotatedString, style = style, modifier = modifier)
65 }
66
67 @Composable
spannableStringToAnnotatedStringnull68 private fun spannableStringToAnnotatedString(
69 text: CharSequence,
70 shouldCapitalize: Boolean,
71 onClickCallbacks: MutableMap<String, (View) -> Unit>,
72 spanColor: Color = MaterialTheme.colors.primary,
73 listener: LinkInteractionListener,
74 ): AnnotatedString {
75 val finalString = if (shouldCapitalize) text.toString().capitalize() else text.toString()
76 val annotatedString =
77 if (text is Spanned) {
78 buildAnnotatedString {
79 append(finalString)
80 for (span in text.getSpans(0, text.length, Any::class.java)) {
81 val start = text.getSpanStart(span)
82 val end = text.getSpanEnd(span)
83 when (span) {
84 is ClickableSpan ->
85 addClickableSpan(
86 span,
87 spanColor,
88 start,
89 end,
90 onClickCallbacks,
91 listener,
92 )
93 else -> addStyle(SpanStyle(), start, end)
94 }
95 }
96 }
97 } else {
98 AnnotatedString(finalString)
99 }
100 return annotatedString
101 }
102
AnnotatedStringnull103 private fun AnnotatedString.Builder.addClickableSpan(
104 span: ClickableSpan,
105 spanColor: Color,
106 start: Int,
107 end: Int,
108 onClickCallbacks: MutableMap<String, (View) -> Unit>,
109 listener: LinkInteractionListener,
110 ) {
111 val key = "${CLICKABLE_SPAN_TAG}:$start:$end"
112 onClickCallbacks[key] = span::onClick
113 addLink(LinkAnnotation.Clickable(key, linkInteractionListener = listener), start, end)
114 addStyle(SpanStyle(color = spanColor, textDecoration = TextDecoration.Underline), start, end)
115 }
116