Implemented drawable parser

This commit is contained in:
Akash Yadav
2022-11-06 07:56:34 +05:30
parent e03ec73fb9
commit c67cbb634c
6 changed files with 88 additions and 62 deletions
Binary file not shown.
@@ -45,8 +45,7 @@ class BitmapDrawableParser protected constructor(parser: XmlPullParser, minDepth
}
val `val` = value(index)
val dr = parseDrawable(context, `val`)
?: throw InflateException("Cannot parse drawable for android:src = $`val`")
val bitmap = BitmapDrawable(BaseApplication.getBaseInstance().resources, toBitmap(dr))
val bitmap = BitmapDrawable(context.resources, toBitmap(dr))
index = attrIndex("antialias")
if (index != -1) {
bitmap.setAntiAlias(parseBoolean(value(index)))
@@ -20,7 +20,6 @@ import android.content.Context
import android.graphics.drawable.ClipDrawable
import android.graphics.drawable.Drawable
import android.view.Gravity
import com.itsaky.androidide.inflater.InflateException
import org.xmlpull.v1.XmlPullParser
/**
@@ -31,17 +30,14 @@ import org.xmlpull.v1.XmlPullParser
open class ClipDrawableParser protected constructor(parser: XmlPullParser, minDepth: Int) :
IDrawableParser(parser, minDepth) {
@Throws(Exception::class)
public override fun parseDrawable(context: Context): Drawable {
public override fun parseDrawable(context: Context): Drawable? {
var index = attrIndex("drawable")
if (index == -1) {
throw InflateException("<clip> drawable must specify android:drawable attribute")
// TODO Parse inner drawables
return ClipDrawable(null, Gravity.LEFT, ClipDrawable.HORIZONTAL)
}
val value =
value(index)
?: throw InflateException("Invalid value specified to android:drawable attribute")
val drawable =
parseDrawable(context, value)
?: throw InflateException("Unable to parse drawable for value: $value")
val value = value(index)
val drawable = parseDrawable(context, value)
var orientation = ClipDrawable.HORIZONTAL
index = attrIndex("clipOrientation")
if (index != -1) {
@@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.annotation.ChecksSdkIntAtLeast
import com.itsaky.androidide.inflater.internal.utils.parseDimension
import org.xmlpull.v1.XmlPullParser
/**
@@ -29,7 +28,8 @@ import org.xmlpull.v1.XmlPullParser
*
* @author Akash Yadav
*/
abstract class IDrawableParser protected constructor(protected open val parser: XmlPullParser?, open var minDepth: Int) {
abstract class IDrawableParser
protected constructor(protected open val parser: XmlPullParser?, open var minDepth: Int) {
/**
* Parse the drawable using the already provided parser and data.
*
@@ -61,40 +61,41 @@ abstract class IDrawableParser protected constructor(protected open val parser:
}
return drawable
}
protected fun parseInteger(value: String?, def: Int): Int {
return parseInteger(value, def)
protected fun parseInteger(value: String, def: Int): Int {
return com.itsaky.androidide.inflater.internal.utils.parseInteger(value, def)
}
protected fun parseBoolean(value: String?): Boolean {
return parseBoolean(value)
protected fun parseBoolean(value: String): Boolean {
return com.itsaky.androidide.inflater.internal.utils.parseBoolean(value)
}
protected fun parseDrawable(context: Context?, value: String?): Drawable {
return parseDrawable(context, value)
protected fun parseDrawable(context: Context, value: String): Drawable {
return com.itsaky.androidide.inflater.internal.utils.parseDrawable(context, value)
}
protected fun parseGravity(value: String?): Int {
return parseGravity(value)
protected fun parseGravity(value: String): Int {
return com.itsaky.androidide.inflater.internal.utils.parseGravity(value)
}
protected fun parseDimension(context: Context?, value: String?): Int {
return parseDimension(context!!, value, 0f).toInt()
return com.itsaky.androidide.inflater.internal.utils
.parseDimension(context!!, value, 0f)
.toInt()
}
protected fun parseColor(context: Context?, value: String?): Int {
return parseColor(context, value)
protected fun parseColor(context: Context, value: String): Int {
return com.itsaky.androidide.inflater.internal.utils.parseColor(context, value)
}
/**
* Actual implementation of the parse logic.
*
* @return The parsed drawable. Maybe `null`.
* @throws Exception If any fatal error occurs while parsing the drawable.
*/
@Throws(Exception::class)
protected abstract fun parseDrawable(context: Context): Drawable?
@Throws(Exception::class) protected abstract fun parseDrawable(context: Context): Drawable?
/**
* Find the index of the attribute with the given name.
*
@@ -109,7 +110,7 @@ abstract class IDrawableParser protected constructor(protected open val parser:
}
return -1
}
/**
* Get the value of the attribute at the given index.
*
@@ -119,28 +120,27 @@ abstract class IDrawableParser protected constructor(protected open val parser:
protected fun value(index: Int): String {
return parser!!.getAttributeValue(index)
}
/**
* Checks if the drawable parser is allowed to parse at the current depth. This must be checked if
* the drawable parser keeps looking for [XmlPullParser.START_TAG] or [ ][XmlPullParser.END_TAG].
*
*
* The [LayerListParser] can contain tags which are expected to be parsed by other
* parsers. So, if the nesting parser does not check if this method if `true`, it might
* consume all the events and the [LayerListParser] will have invalid data to parse. In some
* cases, the nested parser might consume all the events until [XmlPullParser.END_DOCUMENT].
* The [LayerListParser] can contain tags which are expected to be parsed by other parsers. So, if
* the nesting parser does not check if this method if `true`, it might consume all the events and
* the [LayerListParser] will have invalid data to parse. In some cases, the nested parser might
* consume all the events until [XmlPullParser.END_DOCUMENT].
*
* @return `true` if the parser can keep parsing, `false` otherwise.
*/
protected fun canParse(): Boolean {
return parser!!.depth >= minDepth
}
@get:ChecksSdkIntAtLeast(api = VERSION_CODES.Q)
protected val isApi29: Boolean
get() = VERSION.SDK_INT >= 29
companion object {
const val ANY_DEPTH = -1
}
}
}
@@ -25,12 +25,14 @@ import android.util.TypedValue.complexToDimension
import android.view.Gravity
import android.view.ViewGroup.LayoutParams
import androidx.core.text.isDigitsOnly
import com.android.SdkConstants.EXT_XML
import com.android.aaptcompiler.AaptResourceType
import com.android.aaptcompiler.AaptResourceType.ARRAY
import com.android.aaptcompiler.AaptResourceType.ATTR
import com.android.aaptcompiler.AaptResourceType.BOOL
import com.android.aaptcompiler.AaptResourceType.COLOR
import com.android.aaptcompiler.AaptResourceType.DIMEN
import com.android.aaptcompiler.AaptResourceType.DRAWABLE
import com.android.aaptcompiler.AaptResourceType.INTEGER
import com.android.aaptcompiler.AaptResourceType.STRING
import com.android.aaptcompiler.ArrayResource
@@ -38,6 +40,7 @@ import com.android.aaptcompiler.AttributeResource
import com.android.aaptcompiler.BasicString
import com.android.aaptcompiler.BinaryPrimitive
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.FileReference
import com.android.aaptcompiler.RawString
import com.android.aaptcompiler.Reference
import com.android.aaptcompiler.ResourceEntry
@@ -52,8 +55,10 @@ import com.android.aaptcompiler.tryParseBool
import com.android.aaptcompiler.tryParseFlagSymbol
import com.android.aaptcompiler.tryParseInt
import com.android.aaptcompiler.tryParseReference
import com.itsaky.androidide.inflater.drawable.DrawableParserFactory
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.utils.ILogger
import java.io.File
import java.util.regex.Pattern
private var currentModule: AndroidModule? = null
@@ -171,15 +176,27 @@ fun parseDrawable(context: Context, value: String, def: Drawable = unknownDrawab
if (HEX_COLOR.matcher(value).matches()) {
return parseColorDrawable(context, value)
}
// if (value[0] == '@') {
// val (pck, type, name) = parseResourceReference(value)
// return if(pck == null) {
// parseDrawableResRef(type, name, value)
// } else {
// parseQualifiedDrawableResRef()
// }
// }
val drawableResolver: (Value?) -> Drawable? =
fun(it): Drawable? {
if (it is FileReference) {
val file = File(it.path.value())
if (!file.exists() || file.extension != EXT_XML) {
return null
}
val parser = DrawableParserFactory.newParser(context, file) ?: return null
return parser.parse(context)
}
// TODO(itsaky) : Drawable of any type other than a file?
return null
}
if (value[0] == '@') {
return parseReference(
value = value,
expectedType = DRAWABLE,
def = def,
resolver = drawableResolver
)
}
return def
}
@@ -278,7 +295,7 @@ fun parseFloat(value: String, defValue: Float): Float {
@JvmOverloads
fun parseGravity(value: String, def: Int = defaultGravity()): Int {
val attr = findAttributeResource("android", ATTR, "gravity") ?: return defaultGravity()
return parseFlag(attr = attr, value = value, def = defaultGravity())
return parseFlag(attr = attr, value = value, def = def)
}
fun parseFlag(attr: AttributeResource, value: String, def: Int = -1): Int {
@@ -389,19 +406,19 @@ fun <T> resolveResourceReference(
def: T,
resolver: (Value?) -> T?
): T {
val dimenValue = entry.findValue(ConfigDescription())!!.value
if (dimenValue is Reference) {
val value = entry.findValue(ConfigDescription())!!.value
if (value is Reference) {
return resolveResourceReference(
table = table,
type = type,
pck = pck.name,
name = dimenValue.name.entry!!,
name = value.name.entry!!,
def = def,
resolver = resolver
)
}
return resolver(dimenValue)
return resolver(value)
?: run {
log.warn("Unable to resolve dimension reference '$name'")
def
@@ -18,7 +18,12 @@
package com.itsaky.androidide.inflater
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ClipDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.ShapeDrawable
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.inflater.internal.utils.endParse
import com.itsaky.androidide.inflater.internal.utils.parseBoolean
@@ -30,10 +35,10 @@ import com.itsaky.androidide.inflater.internal.utils.parseIntegerArray
import com.itsaky.androidide.inflater.internal.utils.parseString
import com.itsaky.androidide.inflater.internal.utils.parseStringArray
import com.itsaky.androidide.inflater.internal.utils.startParse
import kotlin.math.roundToInt
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.math.roundToInt
@RunWith(RobolectricTestRunner::class)
class ValueParsersTest {
@@ -85,7 +90,7 @@ class ValueParsersTest {
}
@Test
fun `simple color drawable parse test`() {
fun `drawable parse test`() {
inflaterTest { module ->
requiresActivity { activity ->
startParse(module)
@@ -94,6 +99,14 @@ class ValueParsersTest {
assertThat(this).isInstanceOf(ColorDrawable::class.java)
assertThat((this as ColorDrawable).color).isEqualTo(Color.RED)
}
parseDrawable(activity, "@android:drawable/ab_share_pack_material").apply {
assertThat(this).isNotNull()
assertThat(this).isInstanceOf(BitmapDrawable::class.java)
}
parseDrawable(activity, "@android:drawable/action_bar_background").apply {
assertThat(this).isNotNull()
assertThat(this).isInstanceOf(GradientDrawable::class.java)
}
endParse()
}
}
@@ -191,8 +204,9 @@ class ValueParsersTest {
assertThat(parseColor(activity, "@android:color/white")).isEqualTo(Color.WHITE)
assertThat(parseColor(activity, "@android:color/transparent")).isEqualTo(Color.TRANSPARENT)
assertThat(parseColor(activity, "@color/test_color")).isEqualTo(Color.parseColor("#f44336"))
assertThat(parseColor(activity, "@color/test_color_ref")).isEqualTo(Color.parseColor("#f44336"))
assertThat(parseColor(activity, "@color/test_color_ref"))
.isEqualTo(Color.parseColor("#f44336"))
// TODO Implement color state list parser
assertThat(parseColor(activity, "@color/test_selector")).isEqualTo(Color.TRANSPARENT)
endParse()