@@ -49,10 +49,32 @@ import androidx.core.content.res.getColorOrThrow
4949import androidx.core.content.res.use
5050import kotlin.concurrent.getOrSet
5151
52- fun textStyleFromTextAppearance (
52+ /* *
53+ * Returns the given index as a [Color], or [fallbackColor] if the value can't be coerced to a
54+ * [Color].
55+ *
56+ * @param index Index of attribute to retrieve.
57+ * @param fallbackColor Value to return if the attribute is not defined or can't be coerced to a
58+ * [Color].
59+ */
60+ fun TypedArray.parseColor (
61+ index : Int ,
62+ fallbackColor : Color = Color .Unspecified
63+ ): Color = if (hasValue(index)) Color (getColorOrThrow(index)) else fallbackColor
64+
65+ /* *
66+ * Returns the given style resource ID as a [TextStyle].
67+ *
68+ * @param context The current context.
69+ * @param id ID of style resource to retrieve.
70+ * @param density The current display density.
71+ * @param setTextColors Whether to read and set text colors from the style. Defaults to `false`.
72+ * @param defaultFontFamily Optional default font family to use in [TextStyle]s.
73+ */
74+ fun parseTextAppearance (
5375 context : Context ,
54- density : Density ,
5576 @StyleRes id : Int ,
77+ density : Density ,
5678 setTextColors : Boolean ,
5779 defaultFontFamily : FontFamily ?
5880): TextStyle {
@@ -65,22 +87,24 @@ fun textStyleFromTextAppearance(
6587 // Variable fonts are not supported in Compose yet
6688
6789 // FYI, this only works with static font files in assets
68- val fontFamily: FontFamilyWithWeight ? = a.getFontFamilyOrNull (
90+ val fontFamily: FontFamilyWithWeight ? = a.parseFontFamily (
6991 R .styleable.ComposeThemeAdapterTextAppearance_fontFamily
70- ) ? : a.getFontFamilyOrNull (R .styleable.ComposeThemeAdapterTextAppearance_android_fontFamily )
92+ ) ? : a.parseFontFamily (R .styleable.ComposeThemeAdapterTextAppearance_android_fontFamily )
7193
7294 TextStyle (
7395 color = when {
7496 setTextColors -> {
75- a.getComposeColor (R .styleable.ComposeThemeAdapterTextAppearance_android_textColor )
97+ a.parseColor (R .styleable.ComposeThemeAdapterTextAppearance_android_textColor )
7698 }
7799 else -> Color .Unspecified
78100 },
79- fontSize = a.getTextUnit (R .styleable.ComposeThemeAdapterTextAppearance_android_textSize , density),
101+ fontSize = a.parseTextUnit (R .styleable.ComposeThemeAdapterTextAppearance_android_textSize , density),
80102 lineHeight = run {
81- a.getTextUnitOrNull(R .styleable.ComposeThemeAdapterTextAppearance_lineHeight , density)
82- ? : a.getTextUnitOrNull(R .styleable.ComposeThemeAdapterTextAppearance_android_lineHeight , density)
83- ? : TextUnit .Unspecified
103+ a.parseTextUnit(R .styleable.ComposeThemeAdapterTextAppearance_lineHeight , density,
104+ fallbackTextUnit = a.parseTextUnit(
105+ R .styleable.ComposeThemeAdapterTextAppearance_android_lineHeight , density
106+ )
107+ )
84108 },
85109 fontFamily = when {
86110 defaultFontFamily != null -> defaultFontFamily
@@ -113,7 +137,7 @@ fun textStyleFromTextAppearance(
113137 },
114138 fontFeatureSettings = a.getString(R .styleable.ComposeThemeAdapterTextAppearance_android_fontFeatureSettings ),
115139 shadow = run {
116- val shadowColor = a.getComposeColor (R .styleable.ComposeThemeAdapterTextAppearance_android_shadowColor )
140+ val shadowColor = a.parseColor (R .styleable.ComposeThemeAdapterTextAppearance_android_shadowColor )
117141 if (shadowColor != Color .Unspecified ) {
118142 val dx = a.getFloat(R .styleable.ComposeThemeAdapterTextAppearance_android_shadowDx , 0f )
119143 val dy = a.getFloat(R .styleable.ComposeThemeAdapterTextAppearance_android_shadowDy , 0f )
@@ -134,74 +158,13 @@ fun textStyleFromTextAppearance(
134158 }
135159}
136160
137- fun parseShapeAppearance (
138- context : Context ,
139- @StyleRes id : Int ,
140- fallbackShape : CornerBasedShape ,
141- layoutDirection : LayoutDirection
142- ): CornerBasedShape {
143- return context.obtainStyledAttributes(id, R .styleable.ComposeThemeAdapterShapeAppearance ).use { a ->
144- val defaultCornerSize = a.getCornerSizeOrNull(
145- R .styleable.ComposeThemeAdapterShapeAppearance_cornerSize
146- )
147- val cornerSizeTL = a.getCornerSizeOrNull(
148- R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeTopLeft
149- )
150- val cornerSizeTR = a.getCornerSizeOrNull(
151- R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeTopRight
152- )
153- val cornerSizeBL = a.getCornerSizeOrNull(
154- R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeBottomLeft
155- )
156- val cornerSizeBR = a.getCornerSizeOrNull(
157- R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeBottomRight
158- )
159- val isRtl = layoutDirection == LayoutDirection .Rtl
160- val cornerSizeTS = if (isRtl) cornerSizeTR else cornerSizeTL
161- val cornerSizeTE = if (isRtl) cornerSizeTL else cornerSizeTR
162- val cornerSizeBS = if (isRtl) cornerSizeBR else cornerSizeBL
163- val cornerSizeBE = if (isRtl) cornerSizeBL else cornerSizeBR
164-
165- /* *
166- * We do not support the individual `cornerFamilyTopLeft`, etc, since Compose only supports
167- * one corner type per shape. Therefore we only read the `cornerFamily` attribute.
168- */
169- when (a.getInt(R .styleable.ComposeThemeAdapterShapeAppearance_cornerFamily , 0 )) {
170- 0 -> {
171- RoundedCornerShape (
172- topStart = cornerSizeTS ? : defaultCornerSize ? : fallbackShape.topStart,
173- topEnd = cornerSizeTE ? : defaultCornerSize ? : fallbackShape.topEnd,
174- bottomEnd = cornerSizeBE ? : defaultCornerSize ? : fallbackShape.bottomEnd,
175- bottomStart = cornerSizeBS ? : defaultCornerSize ? : fallbackShape.bottomStart
176- )
177- }
178- 1 -> {
179- CutCornerShape (
180- topStart = cornerSizeTS ? : defaultCornerSize ? : fallbackShape.topStart,
181- topEnd = cornerSizeTE ? : defaultCornerSize ? : fallbackShape.topEnd,
182- bottomEnd = cornerSizeBE ? : defaultCornerSize ? : fallbackShape.bottomEnd,
183- bottomStart = cornerSizeBS ? : defaultCornerSize ? : fallbackShape.bottomStart
184- )
185- }
186- else -> throw IllegalArgumentException (" Unknown cornerFamily set in ShapeAppearance" )
187- }
188- }
189- }
190-
191- private val tempTypedValue = ThreadLocal <TypedValue >()
192-
193- fun TypedArray.getComposeColor (
194- index : Int ,
195- fallbackColor : Color = Color .Unspecified
196- ): Color = if (hasValue(index)) Color (getColorOrThrow(index)) else fallbackColor
197-
198161/* *
199- * Returns the given index as a [FontFamily] and [FontWeight],
200- * or `null` if the value can not be coerced to a [FontFamily ].
162+ * Returns the given index as a [FontFamilyWithWeight], or `null` if the value can't be coerced to
163+ * a [FontFamilyWithWeight ].
201164 *
202- * @param index index of attribute to retrieve.
165+ * @param index Index of attribute to retrieve.
203166 */
204- fun TypedArray.getFontFamilyOrNull (index : Int ): FontFamilyWithWeight ? {
167+ fun TypedArray.parseFontFamily (index : Int ): FontFamilyWithWeight ? {
205168 val tv = tempTypedValue.getOrSet(::TypedValue )
206169 if (getValue(index, tv) && tv.type == TypedValue .TYPE_STRING ) {
207170 return when (tv.string) {
@@ -233,10 +196,24 @@ fun TypedArray.getFontFamilyOrNull(index: Int): FontFamilyWithWeight? {
233196 return null
234197}
235198
199+ /* *
200+ * A lightweight class for storing a [FontFamily] and [FontWeight].
201+ */
202+ data class FontFamilyWithWeight (
203+ val fontFamily : FontFamily ,
204+ val weight : FontWeight = FontWeight .Normal
205+ )
206+
207+ /* *
208+ * Returns the given XML resource ID as a [FontFamily], or `null` if the value can't be coerced to
209+ * a [FontFamily].
210+ *
211+ * @param id ID of XML resource to retrieve.
212+ */
236213@SuppressLint(" RestrictedApi" ) // FontResourcesParserCompat.*
237- @RequiresApi(23 ) // XML font families with >1 fonts are only supported on API 23+
238- private fun Resources.parseXmlFontFamily (resourceId : Int ): FontFamily ? {
239- val parser = getXml(resourceId )
214+ @RequiresApi(23 ) // XML font families with > 1 fonts are only supported on API 23+
215+ fun Resources.parseXmlFontFamily (id : Int ): FontFamily ? {
216+ val parser = getXml(id )
240217
241218 // Can't use {} since XmlResourceParser is AutoCloseable, not Closeable
242219 @Suppress(" ConvertTryFinallyToUseCall" )
@@ -272,37 +249,20 @@ private fun fontWeightOf(weight: Int): FontWeight = when (weight) {
272249 else -> FontWeight .W400
273250}
274251
275- data class FontFamilyWithWeight (
276- val fontFamily : FontFamily ,
277- val weight : FontWeight = FontWeight .Normal
278- )
279-
280252/* *
281- * Returns the given index as a [TextUnit], or [fallback ] if the value can not be coerced to
253+ * Returns the given index as a [TextUnit], or [fallbackTextUnit ] if the value can't be coerced to
282254 * a [TextUnit].
283255 *
284- * @param index index of attribute to retrieve.
285- * @param density the current display density.
286- * @param fallback Value to return if the attribute is not defined or cannot be coerced to
287- * a [TextUnit].
256+ * @param index Index of attribute to retrieve.
257+ * @param density The current display density.
258+ * @param fallbackTextUnit Value to return if the attribute is not defined or can't be coerced to a
259+ * [TextUnit].
288260 */
289- internal fun TypedArray.getTextUnit (
261+ fun TypedArray.parseTextUnit (
290262 index : Int ,
291263 density : Density ,
292- fallback : TextUnit = TextUnit .Unspecified
293- ): TextUnit = getTextUnitOrNull(index, density) ? : fallback
294-
295- /* *
296- * Returns the given index as a [TextUnit], or `null` if the value can not be coerced to
297- * a [TextUnit].
298- *
299- * @param index index of attribute to retrieve.
300- * @param density the current display density.
301- */
302- internal fun TypedArray.getTextUnitOrNull (
303- index : Int ,
304- density : Density
305- ): TextUnit ? {
264+ fallbackTextUnit : TextUnit = TextUnit .Unspecified
265+ ): TextUnit {
306266 val tv = tempTypedValue.getOrSet { TypedValue () }
307267 if (getValue(index, tv) && tv.type == TypedValue .TYPE_DIMENSION ) {
308268 return when (tv.complexUnitCompat) {
@@ -315,16 +275,80 @@ internal fun TypedArray.getTextUnitOrNull(
315275 else -> with (density) { getDimension(index, 0f ).toSp() }
316276 }
317277 }
318- return null
278+ return fallbackTextUnit
279+ }
280+
281+ /* *
282+ * Returns the given style resource ID as a [CornerBasedShape], or [fallbackShape] if the value
283+ * can't be coerced to a [CornerBasedShape].
284+ *
285+ * @param context The current context.
286+ * @param id ID of style resource to retrieve.
287+ * @param layoutDirection The current display layout direction.
288+ * @param fallbackShape Value to return if the style resource ID is not defined or can't be coerced
289+ * to a [CornerBasedShape].
290+ */
291+ fun parseShapeAppearance (
292+ context : Context ,
293+ @StyleRes id : Int ,
294+ layoutDirection : LayoutDirection ,
295+ fallbackShape : CornerBasedShape
296+ ): CornerBasedShape {
297+ return context.obtainStyledAttributes(id, R .styleable.ComposeThemeAdapterShapeAppearance ).use { a ->
298+ val defaultCornerSize = a.parseCornerSize(
299+ R .styleable.ComposeThemeAdapterShapeAppearance_cornerSize
300+ )
301+ val cornerSizeTL = a.parseCornerSize(
302+ R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeTopLeft
303+ )
304+ val cornerSizeTR = a.parseCornerSize(
305+ R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeTopRight
306+ )
307+ val cornerSizeBL = a.parseCornerSize(
308+ R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeBottomLeft
309+ )
310+ val cornerSizeBR = a.parseCornerSize(
311+ R .styleable.ComposeThemeAdapterShapeAppearance_cornerSizeBottomRight
312+ )
313+ val isRtl = layoutDirection == LayoutDirection .Rtl
314+ val cornerSizeTS = if (isRtl) cornerSizeTR else cornerSizeTL
315+ val cornerSizeTE = if (isRtl) cornerSizeTL else cornerSizeTR
316+ val cornerSizeBS = if (isRtl) cornerSizeBR else cornerSizeBL
317+ val cornerSizeBE = if (isRtl) cornerSizeBL else cornerSizeBR
318+
319+ /* *
320+ * We do not support the individual `cornerFamilyTopLeft`, etc, since Compose only supports
321+ * one corner type per shape. Therefore we only read the `cornerFamily` attribute.
322+ */
323+ when (a.getInt(R .styleable.ComposeThemeAdapterShapeAppearance_cornerFamily , 0 )) {
324+ 0 -> {
325+ RoundedCornerShape (
326+ topStart = cornerSizeTS ? : defaultCornerSize ? : fallbackShape.topStart,
327+ topEnd = cornerSizeTE ? : defaultCornerSize ? : fallbackShape.topEnd,
328+ bottomEnd = cornerSizeBE ? : defaultCornerSize ? : fallbackShape.bottomEnd,
329+ bottomStart = cornerSizeBS ? : defaultCornerSize ? : fallbackShape.bottomStart
330+ )
331+ }
332+ 1 -> {
333+ CutCornerShape (
334+ topStart = cornerSizeTS ? : defaultCornerSize ? : fallbackShape.topStart,
335+ topEnd = cornerSizeTE ? : defaultCornerSize ? : fallbackShape.topEnd,
336+ bottomEnd = cornerSizeBE ? : defaultCornerSize ? : fallbackShape.bottomEnd,
337+ bottomStart = cornerSizeBS ? : defaultCornerSize ? : fallbackShape.bottomStart
338+ )
339+ }
340+ else -> throw IllegalArgumentException (" Unknown cornerFamily set in ShapeAppearance" )
341+ }
342+ }
319343}
320344
321345/* *
322- * Returns the given index as a [CornerSize], or `null` if the value can not be coerced
323- * to a [CornerSize].
346+ * Returns the given index as a [CornerSize], or `null` if the value can't be coerced to a
347+ * [CornerSize].
324348 *
325- * @param index index of attribute to retrieve.
349+ * @param index Index of attribute to retrieve.
326350 */
327- internal fun TypedArray.getCornerSizeOrNull (index : Int ): CornerSize ? {
351+ fun TypedArray.parseCornerSize (index : Int ): CornerSize ? {
328352 val tv = tempTypedValue.getOrSet { TypedValue () }
329353 if (getValue(index, tv)) {
330354 return when (tv.type) {
@@ -352,3 +376,5 @@ private inline val TypedValue.complexUnitCompat
352376 Build .VERSION .SDK_INT > 22 -> complexUnit
353377 else -> TypedValue .COMPLEX_UNIT_MASK and (data shr TypedValue .COMPLEX_UNIT_SHIFT )
354378 }
379+
380+ private val tempTypedValue = ThreadLocal <TypedValue >()
0 commit comments