mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Added basic value parsers
This commit is contained in:
+1
-1
@@ -186,7 +186,7 @@ class AttrAdapterAnnotationProcessor : AbstractProcessor() {
|
||||
|
||||
private fun getViewName(annotation: AttributeAdapter): String {
|
||||
return try {
|
||||
annotation.forClass.qualifiedName
|
||||
annotation.forView.qualifiedName
|
||||
?: throw IllegalStateException("Cannot find type of $annotation")
|
||||
} catch (err: MirroredTypeException) {
|
||||
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).qualifiedName.toString()
|
||||
|
||||
+1
-1
@@ -27,4 +27,4 @@ import kotlin.reflect.KClass
|
||||
*/
|
||||
@Target(CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class AttributeAdapter(val forClass: KClass<out View>)
|
||||
annotation class AttributeAdapter(val forView: KClass<out View>)
|
||||
|
||||
+52
-6
@@ -17,6 +17,8 @@
|
||||
|
||||
package com.itsaky.androidide.projects.api
|
||||
|
||||
import com.android.SdkConstants
|
||||
import com.android.aaptcompiler.AaptResourceType
|
||||
import com.android.aaptcompiler.ResourceTable
|
||||
import com.android.builder.model.v2.ide.LibraryType.ANDROID_LIBRARY
|
||||
import com.android.builder.model.v2.ide.LibraryType.JAVA_LIBRARY
|
||||
@@ -119,7 +121,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
override var moduleData: SimpleModuleData? = null
|
||||
|
||||
|
||||
init {
|
||||
type = Android
|
||||
}
|
||||
@@ -304,15 +306,13 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
|
||||
val resDirs = mainSourceSet?.sourceProvider?.resDirectories ?: return null
|
||||
return ResourceTableRegistry.getInstance().forPackage(this.packageName, *resDirs.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the resource table for this module.
|
||||
*/
|
||||
|
||||
/** Updates the resource table for this module. */
|
||||
fun updateResourceTable() {
|
||||
if (this.packageName == UNKNOWN_PACKAGE) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
CompletableFuture.runAsync {
|
||||
val tableRegistry = ResourceTableRegistry.getInstance()
|
||||
val resDirs = mainSourceSet?.sourceProvider?.resDirectories ?: return@runAsync
|
||||
@@ -369,6 +369,52 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all the resource tables from this module and returns if any of the tables contain
|
||||
* resources for the the given package.
|
||||
*
|
||||
* @param pck The package to look for.
|
||||
*/
|
||||
fun findResourceTableForPackage(pck: String, hasGroup: AaptResourceType? = null): ResourceTable? {
|
||||
if (pck == SdkConstants.ANDROID_PKG) {
|
||||
return getFrameworkResourceTable()
|
||||
}
|
||||
|
||||
val tables: List<ResourceTable> =
|
||||
mutableListOf<ResourceTable>().apply {
|
||||
getResourceTable()?.let { add(it) }
|
||||
addAll(getSourceResourceTables())
|
||||
addAll(getDependencyResourceTables())
|
||||
}
|
||||
|
||||
for (table in tables) {
|
||||
val resPck = table.findPackage(pck) ?: continue
|
||||
if (hasGroup == null) {
|
||||
return table
|
||||
}
|
||||
if (resPck.findGroup(hasGroup) != null) {
|
||||
return table
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the resource tables associated with this module (including the framework resource
|
||||
* table).
|
||||
*
|
||||
* @return The associated resource tables.
|
||||
*/
|
||||
fun getAllResourceTables(): Set<ResourceTable> {
|
||||
return mutableSetOf<ResourceTable>().apply {
|
||||
getResourceTable()?.let { add(it) }
|
||||
getFrameworkResourceTable()?.let { add(it) }
|
||||
addAll(getSourceResourceTables())
|
||||
addAll(getDependencyResourceTables())
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the resource table for the attrs_manifest.xml file. */
|
||||
fun getManifestAttrTable(): ResourceTable? {
|
||||
val platform = getPlatformDir() ?: return null
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.xml.utils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
val attrValue_unqualifiedRef: Pattern by lazy { Pattern.compile("@(\\w+)/(.*)") }
|
||||
val attrValue_qualifiedRef: Pattern by lazy { Pattern.compile("@((\\w|\\.)+):(\\w+)/(.*)") }
|
||||
val attrValue_qualifiedRefWithIncompleteType: Pattern by lazy { Pattern.compile("@((\\w|\\.)+):(\\w*)") }
|
||||
val attrValue_qualifiedRefWithIncompletePckOrType: Pattern by lazy { Pattern.compile("@((\\w|\\.)*)") }
|
||||
+8
-10
@@ -49,7 +49,10 @@ import com.itsaky.androidide.lsp.xml.edits.QualifiedValueEditHandler
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE_VALUE
|
||||
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
|
||||
import java.util.regex.Pattern
|
||||
import com.itsaky.androidide.xml.utils.attrValue_qualifiedRef
|
||||
import com.itsaky.androidide.xml.utils.attrValue_qualifiedRefWithIncompletePckOrType
|
||||
import com.itsaky.androidide.xml.utils.attrValue_qualifiedRefWithIncompleteType
|
||||
import com.itsaky.androidide.xml.utils.attrValue_unqualifiedRef
|
||||
import org.eclipse.lemminx.dom.DOMDocument
|
||||
|
||||
/**
|
||||
@@ -60,11 +63,6 @@ import org.eclipse.lemminx.dom.DOMDocument
|
||||
open class AttrValueCompletionProvider(provider: ICompletionProvider) :
|
||||
IXmlCompletionProvider(provider) {
|
||||
|
||||
private val unqualifiedRefMatcher by lazy { Pattern.compile("@(\\w+)/(.*)") }
|
||||
private val qualifiedRef by lazy { Pattern.compile("@((\\w|\\.)+):(\\w+)/(.*)") }
|
||||
private val qualifiedRefWithIncompleteType by lazy { Pattern.compile("@((\\w|\\.)+):(\\w*)") }
|
||||
private val qualifiedRefWithIncompletePckOrType by lazy { Pattern.compile("@((\\w|\\.)*)") }
|
||||
|
||||
override fun canProvideCompletions(pathData: ResourcePathData, type: NodeType): Boolean {
|
||||
return super.canProvideCompletions(pathData, type) && type == ATTRIBUTE_VALUE
|
||||
}
|
||||
@@ -121,7 +119,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
|
||||
|
||||
// If user is typign entry with package name and resource type. For example
|
||||
// '@com.itsaky.test.app:string/app_name' or '@android:string/ok'
|
||||
var matcher = qualifiedRef.matcher(value)
|
||||
var matcher = attrValue_qualifiedRef.matcher(value)
|
||||
if (matcher.matches()) {
|
||||
val valPck = matcher.group(1)
|
||||
val typeStr = matcher.group(3)
|
||||
@@ -133,7 +131,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
|
||||
|
||||
// If user is typing qualified reference but with incomplete type
|
||||
// For example: '@android:str' or '@com.itsaky.test.app:str'
|
||||
matcher = qualifiedRefWithIncompleteType.matcher(value)
|
||||
matcher = attrValue_qualifiedRefWithIncompleteType.matcher(value)
|
||||
if (matcher.matches()) {
|
||||
val valPck = matcher.group(1)!!
|
||||
val incompleteType = matcher.group(3) ?: ""
|
||||
@@ -143,7 +141,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
|
||||
|
||||
// If user is typing qualified reference but with incomplete type or package name
|
||||
// For example: '@android:str' or '@str'
|
||||
matcher = qualifiedRefWithIncompletePckOrType.matcher(value)
|
||||
matcher = attrValue_qualifiedRefWithIncompletePckOrType.matcher(value)
|
||||
if (matcher.matches()) {
|
||||
val valPck = matcher.group(1)!!
|
||||
|
||||
@@ -157,7 +155,7 @@ open class AttrValueCompletionProvider(provider: ICompletionProvider) :
|
||||
}
|
||||
|
||||
// If user is typing entry name with resource type. For example '@string/app_name'
|
||||
matcher = unqualifiedRefMatcher.matcher(value)
|
||||
matcher = attrValue_unqualifiedRef.matcher(value)
|
||||
if (matcher.matches()) {
|
||||
val typeStr = matcher.group(1)
|
||||
val newPrefix = matcher.group(2) ?: ""
|
||||
|
||||
Binary file not shown.
@@ -24,14 +24,15 @@ import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.inflater.R
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* View used to show views that cannot be inflated.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ErrorView(context: Context, message: String) :
|
||||
ViewImpl(NAME, createErrView(context, message)) {
|
||||
class ErrorView(file: File, context: Context, message: String) :
|
||||
ViewImpl(file = file, name = NAME, view = createErrView(context, message)) {
|
||||
companion object {
|
||||
private val NAME = View::class.java.name
|
||||
}
|
||||
|
||||
+11
-5
@@ -55,11 +55,17 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
|
||||
override var inflationEventListener: IInflateEventsListener? = null
|
||||
private val log = ILogger.newInstance("LayoutInflaterImpl")
|
||||
private var inflatingFile: File? = null
|
||||
|
||||
protected val file: File
|
||||
get() = this.inflatingFile!!
|
||||
|
||||
override fun inflate(file: File, parent: ViewGroup): IView? {
|
||||
this.inflatingFile = file
|
||||
inflationEventListener?.onEvent(InflationStartEvent())
|
||||
return doInflate(file, wrap(parent)).apply {
|
||||
inflationEventListener?.onEvent(InflationFinishEvent(this))
|
||||
inflatingFile = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +195,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
): IView {
|
||||
return try {
|
||||
val v = createViewInstance(widget, parent)
|
||||
return ViewImpl(widget.qualifiedName, v)
|
||||
return ViewImpl(file, widget.qualifiedName, v)
|
||||
} catch (err: Throwable) {
|
||||
onCreateUnsupportedView("Unable to create view for widget ${widget.qualifiedName}", parent)
|
||||
}
|
||||
@@ -227,11 +233,11 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
}
|
||||
|
||||
private fun onCreateUnsupportedView(message: String, parent: ViewGroup): IView {
|
||||
return ErrorView(parent.context, message)
|
||||
return ErrorView(file, parent.context, message)
|
||||
}
|
||||
|
||||
private fun wrap(parent: ViewGroup) : IViewGroup {
|
||||
return ViewGroupImpl(parent.javaClass.name, parent)
|
||||
|
||||
private fun wrap(parent: ViewGroup): IViewGroup {
|
||||
return ViewGroupImpl(file, parent.javaClass.name, parent)
|
||||
}
|
||||
|
||||
private fun SourcePosition.lineCol(): String {
|
||||
|
||||
@@ -20,9 +20,10 @@ package com.itsaky.androidide.inflater.internal
|
||||
import android.view.ViewGroup
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.IViewGroup
|
||||
import java.io.File
|
||||
|
||||
open class ViewGroupImpl(name: String, view: ViewGroup) :
|
||||
ViewImpl(name = name, view = view), IViewGroup {
|
||||
open class ViewGroupImpl(file: File, name: String, view: ViewGroup) :
|
||||
ViewImpl(file = file, name = name, view = view), IViewGroup {
|
||||
|
||||
protected val children = mutableListOf<IView>()
|
||||
override val childCount: Int
|
||||
|
||||
@@ -24,10 +24,12 @@ import com.itsaky.androidide.inflater.IViewGroup
|
||||
import com.itsaky.androidide.inflater.internal.utils.simpleName
|
||||
import com.itsaky.androidide.inflater.internal.utils.tagName
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.File
|
||||
|
||||
open class ViewImpl
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
internal val file: File,
|
||||
override val name: String,
|
||||
override val view: View,
|
||||
override val simpleName: String = name.simpleName(),
|
||||
|
||||
+67
-2
@@ -17,25 +17,90 @@
|
||||
|
||||
package com.itsaky.androidide.inflater.internal.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.android.SdkConstants
|
||||
import com.itsaky.androidide.annotations.inflater.AttributeAdapter
|
||||
import com.itsaky.androidide.inflater.IAttribute
|
||||
import com.itsaky.androidide.inflater.IAttributeAdapter
|
||||
import com.itsaky.androidide.inflater.INamespace
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.internal.ViewImpl
|
||||
import com.itsaky.androidide.inflater.internal.utils.endParse
|
||||
import com.itsaky.androidide.inflater.internal.utils.startParse
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Attribute adapter for [View].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@AttributeAdapter(forClass = View::class)
|
||||
@AttributeAdapter(forView = View::class)
|
||||
open class ViewAttrAdapter : IAttributeAdapter() {
|
||||
|
||||
override fun apply(view: IView, attribute: IAttribute) {
|
||||
TODO("Not yet implemented")
|
||||
doApply<View>(view, attribute) { file, context, layoutParams, namespace, name, value ->
|
||||
var handled = true
|
||||
when (name) {
|
||||
"layout_height" -> layoutParams.height = parseDimension(context, value)
|
||||
"layout_width" -> layoutParams.width = parseDimension(context, value)
|
||||
"alpha" -> alpha = parseFloat(value)
|
||||
else -> handled = false
|
||||
}
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
|
||||
protected fun parseDimension(context: Context, value: String): Int {
|
||||
return com.itsaky.androidide.inflater.internal.utils.parseDimension(context, value)
|
||||
}
|
||||
|
||||
protected fun parseFloat(value: String, def: Float = 1f): Float {
|
||||
return com.itsaky.androidide.inflater.internal.utils.parseFloat(value, def)
|
||||
}
|
||||
|
||||
override fun applyBasic(view: IView) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
protected open fun canHandleNamespace(namespace: INamespace): Boolean {
|
||||
return this.canHandleNamespace(namespace.uri)
|
||||
}
|
||||
protected open fun canHandleNamespace(nsUri: String): Boolean {
|
||||
return SdkConstants.ANDROID_URI == nsUri
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides easy access to various properties related to the view and attribute when applying an
|
||||
* attributes.
|
||||
*/
|
||||
protected fun <T : View> doApply(
|
||||
view: IView,
|
||||
attribute: IAttribute,
|
||||
block:
|
||||
T.(
|
||||
file: File,
|
||||
context: Context,
|
||||
layoutParams: ViewGroup.LayoutParams,
|
||||
namespace: INamespace,
|
||||
name: String,
|
||||
value: String
|
||||
) -> Unit
|
||||
) {
|
||||
if (!canHandleNamespace(attribute.namespace)) {
|
||||
return
|
||||
}
|
||||
(view.view as T).apply {
|
||||
val file = (view as ViewImpl).file
|
||||
val module =
|
||||
ProjectManager.findModuleForFile(file) as? AndroidModule
|
||||
?: throw IllegalStateException("Cannot find module for file: $file")
|
||||
startParse(module)
|
||||
block(file, context, layoutParams, attribute.namespace, attribute.name, attribute.value)
|
||||
endParse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.inflater.internal.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.TypedValue.COMPLEX_UNIT_DIP
|
||||
import android.util.TypedValue.COMPLEX_UNIT_IN
|
||||
import android.util.TypedValue.COMPLEX_UNIT_MM
|
||||
import android.util.TypedValue.COMPLEX_UNIT_PT
|
||||
import android.util.TypedValue.COMPLEX_UNIT_PX
|
||||
import android.util.TypedValue.COMPLEX_UNIT_SP
|
||||
import android.util.TypedValue.applyDimension
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import com.android.aaptcompiler.AaptResourceType.DIMEN
|
||||
import com.android.aaptcompiler.BinaryPrimitive
|
||||
import com.android.aaptcompiler.ConfigDescription
|
||||
import com.android.aaptcompiler.Reference
|
||||
import com.android.aaptcompiler.ResourceEntry
|
||||
import com.android.aaptcompiler.ResourceGroup
|
||||
import com.android.aaptcompiler.ResourceName
|
||||
import com.android.aaptcompiler.ResourceTable
|
||||
import com.android.aaptcompiler.ResourceTablePackage
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.xml.utils.attrValue_qualifiedRef
|
||||
import com.itsaky.androidide.xml.utils.attrValue_unqualifiedRef
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private var currentModule: AndroidModule? = null
|
||||
private val log = ILogger.newInstance("ParseUtilsKt")
|
||||
private val HEX_COLOR: Pattern = Pattern.compile("#[a-fA-F\\d]{6,8}")
|
||||
|
||||
val module: AndroidModule
|
||||
get() =
|
||||
currentModule ?: throw IllegalStateException("You must call startParse(AndroidModule) first")
|
||||
|
||||
fun startParse(m: AndroidModule) {
|
||||
currentModule = m
|
||||
}
|
||||
|
||||
fun endParse() {
|
||||
currentModule = null
|
||||
}
|
||||
|
||||
fun parseDrawable(context: Context, value: String): Drawable {
|
||||
if (HEX_COLOR.matcher(value).matches()) {
|
||||
return parseColorDrawable(value)
|
||||
}
|
||||
|
||||
// if (value[0] == '@') {
|
||||
// val (pck, type, name) = parseResourceReference(value)
|
||||
// return if(pck == null) {
|
||||
// parseDrawableResRef()
|
||||
// } else {
|
||||
// parseQualifiedDrawableResRef()
|
||||
// }
|
||||
// }
|
||||
return newColorDrawable(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
fun parseColorDrawable(value: String): Drawable {
|
||||
val color =
|
||||
try {
|
||||
Color.parseColor(value)
|
||||
} catch (err: Throwable) {
|
||||
log.warn("Unable to parse color code", value)
|
||||
Color.TRANSPARENT
|
||||
}
|
||||
return newColorDrawable(color)
|
||||
}
|
||||
|
||||
fun newColorDrawable(color: Int): Drawable {
|
||||
return ColorDrawable(color)
|
||||
}
|
||||
|
||||
fun parseDimension(
|
||||
context: Context,
|
||||
value: String?,
|
||||
defValue: Int = LayoutParams.WRAP_CONTENT,
|
||||
): Int {
|
||||
if (value.isNullOrBlank()) {
|
||||
return defValue
|
||||
}
|
||||
val c = value[0]
|
||||
if (c.isDigit()) {
|
||||
val i = value.length - 2
|
||||
val dimension = parseFloat(value.substring(0, i), defValue.toFloat())
|
||||
val unit = parseDimensionUnit(value.substring(i))
|
||||
return applyDimension(unit, dimension, context.resources.displayMetrics).toInt()
|
||||
} else if (c == '@') {
|
||||
val (pck, type, name) = parseResourceReference(value)
|
||||
return if (pck == null) {
|
||||
parseDimensionResRef(type, name, value)
|
||||
} else {
|
||||
parseQualifiedDimensionResRef(type, value, pck, name)
|
||||
}
|
||||
} else {
|
||||
return when (value) {
|
||||
"wrap_content" -> LayoutParams.WRAP_CONTENT
|
||||
"fill_parent",
|
||||
"match_parent", -> LayoutParams.MATCH_PARENT
|
||||
else -> {
|
||||
log.warn("Cannot infer type of dimension resource: '$value'")
|
||||
LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parseFloat(value: String, defValue: Float): Float {
|
||||
return try {
|
||||
value.toFloat()
|
||||
} catch (err: Throwable) {
|
||||
defValue
|
||||
}
|
||||
}
|
||||
|
||||
fun parseDimensionUnit(unitStr: String): Int {
|
||||
return when (unitStr) {
|
||||
"dp" -> COMPLEX_UNIT_DIP
|
||||
"sp" -> COMPLEX_UNIT_SP
|
||||
"pt" -> COMPLEX_UNIT_PT
|
||||
"px" -> COMPLEX_UNIT_PX
|
||||
"in" -> COMPLEX_UNIT_IN
|
||||
"mm" -> COMPLEX_UNIT_MM
|
||||
else -> COMPLEX_UNIT_DIP
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDimensionResRef(type: String, name: String, value: String?): Int {
|
||||
if (type.isBlank() || name.isBlank()) {
|
||||
throw IllegalArgumentException("Cannot parse resource reference: '$value'")
|
||||
}
|
||||
// Must be a dimension type
|
||||
if (type != "dimen") {
|
||||
throw IllegalArgumentException("A dimension value is expect. Current value is $value")
|
||||
}
|
||||
|
||||
var resTable: ResourceTable? = null
|
||||
var resGrp: ResourceGroup? = null
|
||||
var resPck: ResourceTablePackage? = null
|
||||
var resEntry: ResourceEntry? = null
|
||||
for (t in module.getAllResourceTables()) {
|
||||
val result = t.findResource(ResourceName("", DIMEN, name)) ?: continue
|
||||
resTable = t
|
||||
resGrp = result.group
|
||||
resPck = result.tablePackage
|
||||
resEntry = result.entry
|
||||
break
|
||||
}
|
||||
|
||||
return resolveDimensionReference(
|
||||
table = resTable!!,
|
||||
group = resGrp!!,
|
||||
entry = resEntry!!,
|
||||
pck = resPck!!.name,
|
||||
name = name
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseQualifiedDimensionResRef(
|
||||
type: String,
|
||||
value: String?,
|
||||
pck: String,
|
||||
name: String
|
||||
): Int {
|
||||
// Must be a dimension type
|
||||
if (type != "dimen") {
|
||||
throw IllegalArgumentException("A dimension value is expect. Current value is $value")
|
||||
}
|
||||
|
||||
val table =
|
||||
module.findResourceTableForPackage(pck, DIMEN)
|
||||
?: throw IllegalArgumentException("Resource table for package '$pck' not found.")
|
||||
|
||||
return resolveDimensionReference(table = table, group = null, pck = pck, name = name)
|
||||
}
|
||||
|
||||
private fun resolveDimensionReference(
|
||||
table: ResourceTable,
|
||||
group: ResourceGroup? = null,
|
||||
entry: ResourceEntry? = null,
|
||||
pck: String,
|
||||
name: String,
|
||||
): Int {
|
||||
val grp = group ?: table.findPackage(pck)!!.findGroup(DIMEN)!!
|
||||
val e =
|
||||
entry
|
||||
?: grp.findEntry(name)
|
||||
?: run {
|
||||
log.warn("Dimension resource with name '$name' not found")
|
||||
return LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
val dimenValue = e.findValue(ConfigDescription())!!.value
|
||||
if (dimenValue is Reference) {
|
||||
return resolveDimensionReference(
|
||||
table = table,
|
||||
group = grp,
|
||||
entry = null,
|
||||
pck = pck,
|
||||
name = dimenValue.name.entry!!
|
||||
)
|
||||
} else if (dimenValue is BinaryPrimitive) {
|
||||
return dimenValue.resValue.data
|
||||
}
|
||||
|
||||
log.warn("Unable to resolve dimension reference '$name'")
|
||||
return LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
|
||||
private fun parseResourceReference(value: String): Triple<String?, String, String> {
|
||||
// A qualified resource reference
|
||||
// For example: '@com.itsaky.androidide.resources:dimen/my_dimen' or '@android:dimen/my_dimen'
|
||||
// ^pck ^type ^name
|
||||
var matcher = attrValue_qualifiedRef.matcher(value)
|
||||
if (matcher.matches()) {
|
||||
return Triple(matcher.group(1)!!, matcher.group(3)!!, matcher.group(4)!!)
|
||||
}
|
||||
|
||||
matcher = attrValue_unqualifiedRef.matcher(value)
|
||||
if (matcher.matches()) {
|
||||
return Triple<String?, String, String>(null, matcher.group(1)!!, matcher.group(2)!!)
|
||||
}
|
||||
|
||||
return Triple<String?, String, String>(null, "", "")
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.inflater
|
||||
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -36,30 +37,21 @@ import org.robolectric.android.controller.ActivityController
|
||||
/** @author Akash Yadav */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class LayoutInflaterTest {
|
||||
|
||||
private lateinit var activityController: ActivityController<AppCompatActivity>
|
||||
private lateinit var activity: AppCompatActivity
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
|
||||
val (server, project) = ToolingApiTestLauncher().launchServer(implDir = "../build-tools/tooling-api-impl")
|
||||
server.initialize(InitializeProjectMessage(File("../tests/test-project").absolutePath)).get()
|
||||
|
||||
Lookup.DEFAULT.register(BuildService.KEY_PROJECT_PROXY, project)
|
||||
ProjectManager.setupProject()
|
||||
|
||||
this.activityController = Robolectric.buildActivity(AppCompatActivity::class.java)
|
||||
this.activity = this.activityController.get()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `test functionality`() {
|
||||
val file = layoutFile("singleView")
|
||||
val parent = LinearLayout(activity)
|
||||
val inflater = ILayoutInflater.newInflater()
|
||||
val inflated = inflater.inflate(file, parent)
|
||||
assertThat(inflated).isNotNull()
|
||||
inflaterTest {
|
||||
requiresActivity { activity ->
|
||||
val file = layoutFile("singleView")
|
||||
val parent = LinearLayout(activity)
|
||||
val inflater = ILayoutInflater.newInflater()
|
||||
val inflated = inflater.inflate(file, parent)
|
||||
inflated.apply {
|
||||
assertThat(this).isNotNull()
|
||||
assertThat(this).isInstanceOf(View::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun layoutFile(name: String): File {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.inflater
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.inflater.internal.utils.endParse
|
||||
import com.itsaky.androidide.inflater.internal.utils.parseDimension
|
||||
import com.itsaky.androidide.inflater.internal.utils.parseDrawable
|
||||
import com.itsaky.androidide.inflater.internal.utils.startParse
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ValueParsersTest {
|
||||
|
||||
@Test
|
||||
fun `hardcoded dimension parser test`() {
|
||||
inflaterTest { module ->
|
||||
requiresActivity { activity ->
|
||||
startParse(module)
|
||||
val units = arrayOf("dp", "sp", "pt", "px", "in", "mm")
|
||||
val results = intArrayOf(1, 1, 2, 1, 160, 6)
|
||||
for (i in units.indices) {
|
||||
assertThat(parseDimension(activity, "1" + units[i], -1)).isEqualTo(results[i])
|
||||
}
|
||||
endParse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reference dimension parser test`() {
|
||||
inflaterTest { module ->
|
||||
requiresActivity { activity ->
|
||||
startParse(module)
|
||||
assertThat(parseDimension(activity, "@android:dimen/app_icon_size")).isEqualTo(12289)
|
||||
assertThat(parseDimension(activity, "@android:dimen/status_bar_height_portrait"))
|
||||
.isEqualTo(6145)
|
||||
|
||||
// The app module here targets android 31 and the dimension resource below was added in
|
||||
// android 31. So, the resource should not be resolved and
|
||||
// ViewGroup.LayoutParams.WRAP_CONTENT must be returned instead.
|
||||
assertThat(parseDimension(activity, "@android:dimen/status_bar_height_default"))
|
||||
.isEqualTo(-2)
|
||||
endParse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `simple color drawable parse test`() {
|
||||
inflaterTest { module ->
|
||||
requiresActivity { activity ->
|
||||
startParse(module)
|
||||
parseDrawable(activity, "#ff0000").apply {
|
||||
assertThat(this).isNotNull()
|
||||
assertThat(this).isInstanceOf(ColorDrawable::class.java)
|
||||
assertThat((this as ColorDrawable).color).isEqualTo(Color.RED)
|
||||
}
|
||||
endParse()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.inflater
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.itsaky.androidide.lookup.Lookup
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import com.itsaky.androidide.projects.builder.BuildService
|
||||
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
|
||||
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
|
||||
import java.io.File
|
||||
import org.junit.Ignore
|
||||
import org.robolectric.Robolectric
|
||||
|
||||
@Ignore("Test utility provider")
|
||||
object XmlInflaterTest {
|
||||
|
||||
private var init: Boolean = false
|
||||
internal val activity by lazy { Robolectric.buildActivity(AppCompatActivity::class.java).get() }
|
||||
|
||||
fun initIfNeeded() {
|
||||
if (init) {
|
||||
return
|
||||
}
|
||||
|
||||
val (server, project) =
|
||||
ToolingApiTestLauncher().launchServer(implDir = "../build-tools/tooling-api-impl")
|
||||
server.initialize(InitializeProjectMessage(File("../tests/test-project").absolutePath)).get()
|
||||
|
||||
Lookup.DEFAULT.register(BuildService.KEY_PROJECT_PROXY, project)
|
||||
ProjectManager.setupProject()
|
||||
init = true
|
||||
}
|
||||
}
|
||||
|
||||
fun inflaterTest(block: (AndroidModule) -> Unit) {
|
||||
XmlInflaterTest.initIfNeeded()
|
||||
block(ProjectManager.app!!)
|
||||
}
|
||||
|
||||
fun requiresActivity(block: (AppCompatActivity) -> Unit) {
|
||||
block(XmlInflaterTest.activity)
|
||||
}
|
||||
Reference in New Issue
Block a user