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