1 /*
<lambda>null2 * Copyright 2021 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
17 package androidx.glance.appwidget.translators
18
19 import android.graphics.Typeface
20 import android.os.Build
21 import android.text.Layout.Alignment
22 import android.text.ParcelableSpan
23 import android.text.SpannableString
24 import android.text.Spanned
25 import android.text.style.AlignmentSpan
26 import android.text.style.StrikethroughSpan
27 import android.text.style.StyleSpan
28 import android.text.style.TextAppearanceSpan
29 import android.text.style.TypefaceSpan
30 import android.text.style.UnderlineSpan
31 import android.util.Log
32 import android.util.TypedValue
33 import android.view.Gravity
34 import android.widget.RemoteViews
35 import androidx.annotation.RequiresApi
36 import androidx.compose.ui.graphics.toArgb
37 import androidx.core.widget.RemoteViewsCompat.setTextViewGravity
38 import androidx.core.widget.RemoteViewsCompat.setTextViewMaxLines
39 import androidx.core.widget.RemoteViewsCompat.setTextViewTextColor
40 import androidx.core.widget.RemoteViewsCompat.setTextViewTextColorResource
41 import androidx.glance.appwidget.GlanceAppWidgetTag
42 import androidx.glance.appwidget.LayoutType
43 import androidx.glance.appwidget.R
44 import androidx.glance.appwidget.TranslationContext
45 import androidx.glance.appwidget.applyModifiers
46 import androidx.glance.appwidget.insertView
47 import androidx.glance.color.DayNightColorProvider
48 import androidx.glance.text.EmittableText
49 import androidx.glance.text.FontStyle
50 import androidx.glance.text.FontWeight
51 import androidx.glance.text.TextAlign
52 import androidx.glance.text.TextDecoration
53 import androidx.glance.text.TextStyle
54 import androidx.glance.unit.FixedColorProvider
55 import androidx.glance.unit.ResourceColorProvider
56
57 internal fun RemoteViews.translateEmittableText(
58 translationContext: TranslationContext,
59 element: EmittableText
60 ) {
61 val viewDef = insertView(translationContext, LayoutType.Text, element.modifier)
62 setText(
63 translationContext,
64 viewDef.mainViewId,
65 element.text,
66 element.style,
67 maxLines = element.maxLines,
68 )
69 applyModifiers(translationContext, this, element.modifier, viewDef)
70 }
71
setTextnull72 internal fun RemoteViews.setText(
73 translationContext: TranslationContext,
74 resId: Int,
75 text: String,
76 style: TextStyle?,
77 maxLines: Int,
78 verticalTextGravity: Int = Gravity.TOP,
79 ) {
80 if (maxLines != Int.MAX_VALUE) {
81 setTextViewMaxLines(resId, maxLines)
82 }
83
84 if (style == null) {
85 setTextViewText(resId, text)
86 return
87 }
88 val content = SpannableString(text)
89 val length = content.length
90
91 // TODO(b/203656358): Can we support Em here too?
92 style.fontSize?.let {
93 if (!it.isSp) {
94 throw IllegalArgumentException("Only Sp is currently supported for font sizes")
95 }
96 setTextViewTextSize(resId, TypedValue.COMPLEX_UNIT_SP, it.value)
97 }
98 val spans = mutableListOf<ParcelableSpan>()
99 style.textDecoration?.let {
100 if (TextDecoration.LineThrough in it) {
101 spans.add(StrikethroughSpan())
102 }
103 if (TextDecoration.Underline in it) {
104 spans.add(UnderlineSpan())
105 }
106 }
107 style.fontStyle?.let {
108 spans.add(StyleSpan(if (it == FontStyle.Italic) Typeface.ITALIC else Typeface.NORMAL))
109 }
110 style.fontWeight?.let {
111 val textAppearance =
112 when (it) {
113 FontWeight.Bold -> R.style.Glance_AppWidget_TextAppearance_Bold
114 FontWeight.Medium -> R.style.Glance_AppWidget_TextAppearance_Medium
115 else -> R.style.Glance_AppWidget_TextAppearance_Normal
116 }
117 spans.add(TextAppearanceSpan(translationContext.context, textAppearance))
118 }
119 style.fontFamily?.let { family -> spans.add(TypefaceSpan(family.family)) }
120 style.textAlign?.let { align ->
121 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
122 TextTranslatorApi31Impl.setTextViewGravity(
123 this,
124 resId,
125 align.toGravity() or verticalTextGravity
126 )
127 } else {
128 spans.add(AlignmentSpan.Standard(align.toAlignment(translationContext.isRtl)))
129 }
130 }
131 spans.forEach { span -> content.setSpan(span, 0, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) }
132 setTextViewText(resId, content)
133
134 when (val colorProvider = style.color) {
135 is FixedColorProvider -> setTextColor(resId, colorProvider.color.toArgb())
136 is ResourceColorProvider -> {
137 if (Build.VERSION.SDK_INT >= 31) {
138 setTextViewTextColorResource(resId, colorProvider.resId)
139 } else {
140 setTextColor(resId, colorProvider.getColor(translationContext.context).toArgb())
141 }
142 }
143 is DayNightColorProvider -> {
144 if (Build.VERSION.SDK_INT >= 31) {
145 setTextViewTextColor(
146 resId,
147 notNight = colorProvider.day.toArgb(),
148 night = colorProvider.night.toArgb()
149 )
150 } else {
151 setTextColor(resId, colorProvider.getColor(translationContext.context).toArgb())
152 }
153 }
154 else -> Log.w(GlanceAppWidgetTag, "Unexpected text color: $colorProvider")
155 }
156 }
157
toGravitynull158 private fun TextAlign.toGravity(): Int =
159 when (this) {
160 TextAlign.Center -> Gravity.CENTER_HORIZONTAL
161 TextAlign.Left -> Gravity.LEFT
162 TextAlign.Right -> Gravity.RIGHT
163 TextAlign.Start -> Gravity.START
164 TextAlign.End -> Gravity.END
165 else -> {
166 Log.w(GlanceAppWidgetTag, "Unknown TextAlign: $this")
167 Gravity.START
168 }
169 }
170
toAlignmentnull171 private fun TextAlign.toAlignment(isRtl: Boolean): Alignment =
172 when (this) {
173 TextAlign.Center -> Alignment.ALIGN_CENTER
174 TextAlign.Left -> if (isRtl) Alignment.ALIGN_OPPOSITE else Alignment.ALIGN_NORMAL
175 TextAlign.Right -> if (isRtl) Alignment.ALIGN_NORMAL else Alignment.ALIGN_OPPOSITE
176 TextAlign.Start -> Alignment.ALIGN_NORMAL
177 TextAlign.End -> Alignment.ALIGN_OPPOSITE
178 else -> {
179 Log.w(GlanceAppWidgetTag, "Unknown TextAlign: $this")
180 Alignment.ALIGN_NORMAL
181 }
182 }
183
184 @RequiresApi(Build.VERSION_CODES.S)
185 private object TextTranslatorApi31Impl {
setTextViewGravitynull186 fun setTextViewGravity(rv: RemoteViews, viewId: Int, gravity: Int) {
187 rv.setTextViewGravity(viewId, gravity)
188 }
189 }
190