diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar
index 354c0b41b..e91c759b5 100644
Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ
diff --git a/tests/test-project/app/src/main/res/layout/include.xml b/tests/test-project/app/src/main/res/layout/include.xml
index 3c92d4f98..95d4a9e89 100644
--- a/tests/test-project/app/src/main/res/layout/include.xml
+++ b/tests/test-project/app/src/main/res/layout/include.xml
@@ -4,6 +4,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+
diff --git a/tests/test-project/app/src/main/res/layout/layout_include.xml b/tests/test-project/app/src/main/res/layout/layout_include.xml
index 1b75662a9..0c4a7e97d 100644
--- a/tests/test-project/app/src/main/res/layout/layout_include.xml
+++ b/tests/test-project/app/src/main/res/layout/layout_include.xml
@@ -5,17 +5,6 @@
android:layout_height="match_parent"
android:gravity="center">
-
-
-
+
diff --git a/tests/test-project/app/src/main/res/layout/layout_merge.xml b/tests/test-project/app/src/main/res/layout/layout_merge.xml
new file mode 100644
index 000000000..d5a58431d
--- /dev/null
+++ b/tests/test-project/app/src/main/res/layout/layout_merge.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/test-project/app/src/main/res/layout/merge.xml b/tests/test-project/app/src/main/res/layout/merge.xml
new file mode 100644
index 000000000..4933d2438
--- /dev/null
+++ b/tests/test-project/app/src/main/res/layout/merge.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/uidesigner/src/main/java/com/itsaky/androidide/uidesigner/fragments/DesignerWorkspaceFragment.kt b/uidesigner/src/main/java/com/itsaky/androidide/uidesigner/fragments/DesignerWorkspaceFragment.kt
index ea865119d..c22155bcf 100644
--- a/uidesigner/src/main/java/com/itsaky/androidide/uidesigner/fragments/DesignerWorkspaceFragment.kt
+++ b/uidesigner/src/main/java/com/itsaky/androidide/uidesigner/fragments/DesignerWorkspaceFragment.kt
@@ -114,4 +114,4 @@ class DesignerWorkspaceFragment : BaseFragment() {
viewModel.file = file
}
}
-}
+}
\ No newline at end of file
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt
index d95d1fbb2..fa00bdbba 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt
@@ -37,7 +37,7 @@ abstract class ILayoutInflater {
* @param file The file to inflate.
* @param parent The parent view.
*/
- abstract fun inflate(file: File, parent: ViewGroup): IView?
+ abstract fun inflate(file: File, parent: ViewGroup): List
companion object {
@JvmStatic
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt
index 9f0654c8e..5a05c5a21 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt
@@ -47,7 +47,7 @@ class InflationStartEvent : IInflationEvent(Unit)
*
* @property view The inflated view.
*/
-class InflationFinishEvent(view: IView?) : IInflationEvent(view)
+class InflationFinishEvent(views: List) : IInflationEvent>(views)
/**
* Indicates that the given view has been inflated. The view can be anywhere in the layout.
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt
index c920d4897..6df766186 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt
@@ -21,13 +21,16 @@ import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
+import com.android.SdkConstants.EXT_XML
import com.android.aapt.Resources.SourcePosition
+import com.android.aapt.Resources.XmlAttribute
import com.android.aapt.Resources.XmlElement
import com.android.aapt.Resources.XmlNode.NodeCase.ELEMENT
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.LAYOUT
import com.android.aaptcompiler.BlameLogger
import com.android.aaptcompiler.ResourceFile
+import com.android.aaptcompiler.ResourceFile.Type.ProtoXml
import com.android.aaptcompiler.ResourceName
import com.android.aaptcompiler.XmlProcessor
import com.android.aaptcompiler.extractPathData
@@ -42,6 +45,7 @@ import com.itsaky.androidide.inflater.InflationStartEvent
import com.itsaky.androidide.inflater.OnApplyAttributeEvent
import com.itsaky.androidide.inflater.OnInflateViewEvent
import com.itsaky.androidide.inflater.internal.utils.IDTable
+import com.itsaky.androidide.inflater.internal.utils.parseLayoutReference
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.utils.ILogger
@@ -68,7 +72,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
protected val currentLayoutFile: LayoutFile
get() = this._currentLayoutFile!!
- override fun inflate(file: File, parent: ViewGroup): IView? {
+ override fun inflate(file: File, parent: ViewGroup): List {
this._primaryInflatingFile = file
IDTable.newRound()
inflationEventListener?.onEvent(InflationStartEvent())
@@ -78,32 +82,8 @@ open class LayoutInflaterImpl : ILayoutInflater() {
}
}
- protected open fun doInflate(file: File, parent: ViewGroup): IView? {
- val pathData = extractPathData(file)
- if (pathData.type != LAYOUT) {
- throw InflateException("File is not a layout file.")
- }
-
- if (ProjectManager.rootProject == null) {
- throw InflateException("Project is not initialized!")
- }
-
- val module =
- ProjectManager.findModuleForFile(file) as? AndroidModule
- ?: throw InflateException(
- "Cannot find module for given file. Is the project initialized?"
- )
- val resFile =
- ResourceFile(
- ResourceName(module.packageName, pathData.type!!, pathData.name),
- pathData.config,
- pathData.source,
- ResourceFile.Type.ProtoXml
- )
-
- val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
- processor.process(resFile, file.inputStream())
-
+ protected open fun doInflate(file: File, parent: ViewGroup): List {
+ val (processor, module) = processXmlFile(file)
return doInflate(processor, parent, module)
}
@@ -111,7 +91,15 @@ open class LayoutInflaterImpl : ILayoutInflater() {
processor: XmlProcessor,
parent: ViewGroup,
module: AndroidModule,
- ): IView? {
+ ): List {
+ return doInflate(processor, module) { wrap(parent) }
+ }
+
+ protected open fun doInflate(
+ processor: XmlProcessor,
+ module: AndroidModule,
+ parent: () -> IViewGroup,
+ ): List {
// TODO(itsaky) : Add test for multiple view as root layout
// The inflater should fail in such cases
val (file, node) =
@@ -132,11 +120,11 @@ open class LayoutInflaterImpl : ILayoutInflater() {
.forEach { IDTable.set(currentLayoutFile.resName, it.name.entry!!, View.generateViewId()) }
val element = node.element
- val view = onCreateView(element, wrap(parent), module)
+ val views = onCreateView(element, parent(), module)
this._currentLayoutFile = null
- return view
+ return views
}
protected open fun onCreateView(
@@ -146,16 +134,15 @@ open class LayoutInflaterImpl : ILayoutInflater() {
widgets: WidgetTable =
module.getWidgetTable()
?: throw IllegalStateException("No widget table found for module $module"),
- ): IView {
+ ): List {
val parentView = parent.view as ViewGroup
-
- if ("include" == element.name) {
- return onCreateIncludedView(element, parentView, module, widgets)
+ if ("merge" == element.name) {
+ return onCreateMergedView(element, parent, module, widgets)
}
- if ("merge" == element.name) {
- return onCreateMergedView(element, parentView, module, widgets)
+ if ("include" == element.name) {
+ return onCreateIncludedView(element, parent)
}
val widget =
@@ -171,32 +158,8 @@ open class LayoutInflaterImpl : ILayoutInflater() {
})
as ViewImpl
- if (element.namespaceDeclarationCount > 0) {
- for (xmlNamespace in element.namespaceDeclarationList) {
- view.namespaceDecls[xmlNamespace.uri] = NamespaceImpl(xmlNamespace.prefix, xmlNamespace.uri)
- }
- }
-
- val adapter =
- AttributeAdapterIndex.getAdapter(view.name)
- ?: throw InflateException("No attribute adapter found for view ${view.name}")
-
- view.view.layoutParams = generateLayoutParams(parentView)
- parent.addChild(view)
-
- adapter.applyBasic(view)
-
- if (element.attributeCount > 0) {
- for (xmlAttribute in element.attributeList) {
- val namespace =
- view.findNamespaceByUri(xmlAttribute.namespaceUri)
- ?: throw InflateException("Unknown namespace : ${xmlAttribute.namespaceUri}")
- val attr =
- AttributeImpl(namespace = namespace, name = xmlAttribute.name, value = xmlAttribute.value)
- view.addAttribute(attr)
- inflationEventListener?.onEvent(OnApplyAttributeEvent(view, attr))
- }
- }
+ addNamespaceDecls(element, view)
+ applyAttributes(element, view, parent)
if (element.childCount > 0 && view is IViewGroup) {
for (child in element.childList) {
@@ -206,28 +169,94 @@ open class LayoutInflaterImpl : ILayoutInflater() {
onCreateView(element = child.element, parent = view, module = module, widgets = widgets)
}
}
-
+
inflationEventListener?.onEvent(OnInflateViewEvent(view))
- return view
+ return listOf(view)
+ }
+
+ @JvmOverloads
+ protected open fun applyAttributes(
+ element: XmlElement,
+ view: ViewImpl,
+ parent: IViewGroup,
+ attachToParent: Boolean = true,
+ checkAttr: (XmlAttribute) -> Boolean = { true }
+ ) {
+ val parentView = parent.view as ViewGroup
+ val adapter =
+ AttributeAdapterIndex.getAdapter(view.name)
+ ?: throw InflateException("No attribute adapter found for view ${view.name}")
+
+ view.view.layoutParams = generateLayoutParams(parentView)
+
+ if (attachToParent) {
+ parent.addChild(view)
+ }
+
+ adapter.applyBasic(view)
+
+ if (element.attributeCount > 0) {
+ for (xmlAttribute in element.attributeList) {
+ if (!checkAttr(xmlAttribute)) {
+ continue
+ }
+ val namespace =
+ view.findNamespaceByUri(xmlAttribute.namespaceUri)
+ ?: throw InflateException("Unknown namespace : ${xmlAttribute.namespaceUri}")
+ val attr =
+ AttributeImpl(namespace = namespace, name = xmlAttribute.name, value = xmlAttribute.value)
+ view.addAttribute(attr)
+ inflationEventListener?.onEvent(OnApplyAttributeEvent(view, attr))
+ }
+ }
}
private fun onCreateMergedView(
element: XmlElement,
- parent: ViewGroup,
+ parent: IViewGroup,
module: AndroidModule,
widgets: WidgetTable,
- ): IView {
- TODO("Not yet implemented")
+ ): List {
+ val views = mutableListOf()
+ for (xmlNode in element.childList) {
+ if (xmlNode.nodeCase == ELEMENT) {
+ views.addAll(onCreateView(xmlNode.element, parent, module, widgets))
+ }
+ }
+
+ addNamespaceDecls(element, parent as ViewImpl)
+
+ return views
}
- private fun onCreateIncludedView(
- element: XmlElement,
- parent: ViewGroup,
- module: AndroidModule,
- widgets: WidgetTable,
- ): IView {
- TODO("Not yet implemented")
+ private fun onCreateIncludedView(element: XmlElement, parent: IViewGroup): List {
+ val layout =
+ element.attributeList.find { it.namespaceUri.isNullOrBlank() && it.name == "layout" }
+ ?: throw InflateException(" tag must have 'layout' attribute")
+ val file = parseLayoutReference(layout.value)
+ if (file == null || !file.exists() || !file.isFile || file.extension != EXT_XML) {
+ throw InflateException("Invalid layout file reference; '${layout.value}'")
+ }
+
+ val (processor, module) = processXmlFile(file)
+ val inflated = doInflate(processor, module) { parent }
+ if (inflated.isEmpty() || inflated.size > 1) {
+ // probably a merged view or no views at all
+ // no need to apply attributes
+ return inflated
+ }
+
+ val view = inflated[0] as ViewImpl
+ addNamespaceDecls(element = element, view = view)
+
+ // The inflated view is already attached to parent
+ // so we don't need to do it here
+ applyAttributes(element = element, view = view, parent = parent, attachToParent = false) {
+ !(it.namespaceUri.isNullOrBlank() && it.name == "layout")
+ }
+
+ return inflated
}
protected open fun onCreatePlatformView(
@@ -272,6 +301,40 @@ open class LayoutInflaterImpl : ILayoutInflater() {
}
}
+ protected open fun addNamespaceDecls(element: XmlElement, view: ViewImpl) {
+ if (element.namespaceDeclarationCount > 0) {
+ for (xmlNamespace in element.namespaceDeclarationList) {
+ view.namespaceDecls[xmlNamespace.uri] = NamespaceImpl(xmlNamespace.prefix, xmlNamespace.uri)
+ }
+ }
+ }
+
+ protected open fun processXmlFile(file: File): Pair {
+ val pathData = extractPathData(file)
+ if (pathData.type != LAYOUT) {
+ throw InflateException("File is not a layout file.")
+ }
+
+ if (ProjectManager.rootProject == null) {
+ throw InflateException("Project is not initialized!")
+ }
+
+ val module =
+ ProjectManager.findModuleForFile(file) as? AndroidModule
+ ?: throw InflateException("Cannot find module for given file. Is the project initialized?")
+ val resFile =
+ ResourceFile(
+ ResourceName(module.packageName, pathData.type!!, pathData.name),
+ pathData.config,
+ pathData.source,
+ ProtoXml
+ )
+
+ val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
+ processor.process(resFile, file.inputStream())
+ return processor to module
+ }
+
private fun createViewInstance(widget: Widget, parent: ViewGroup): View {
val klass = javaClass.classLoader!!.loadClass(widget.qualifiedName)
val constructor = klass.getConstructor(Context::class.java)
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewGroupImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewGroupImpl.kt
index 1a5f186ce..57ff82e96 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewGroupImpl.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewGroupImpl.kt
@@ -18,10 +18,8 @@
package com.itsaky.androidide.inflater.internal
import android.view.ViewGroup
-import com.android.aaptcompiler.XmlResource
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
-import java.io.File
open class ViewGroupImpl(file: LayoutFile, name: String, view: ViewGroup) :
ViewImpl(file = file, name = name, view = view), IViewGroup {
@@ -68,4 +66,11 @@ open class ViewGroupImpl(file: LayoutFile, name: String, view: ViewGroup) :
addChild(index, view)
return existing
}
+
+ override fun printHierarchy(builder: StringBuilder, indent: Int) {
+ super.printHierarchy(builder, indent)
+ for (child in children) {
+ (child as ViewImpl).printHierarchy(builder, indent + 1)
+ }
+ }
}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt
index 7d7c2683d..ef1a6f953 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt
@@ -59,4 +59,14 @@ constructor(
internal fun findNamespaceByUri(uri: String): INamespace? {
return this.namespaceDecls[uri] ?: (parent as? ViewImpl)?.findNamespaceByUri(uri)
}
+
+ internal open fun printHierarchy() : String {
+ return StringBuilder().apply { printHierarchy(this, 0) }.toString()
+ }
+
+ internal open fun printHierarchy(builder: StringBuilder, indent: Int) {
+ builder.append(" ".repeat(indent * 4))
+ builder.append(name)
+ builder.append("\n")
+ }
}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt
index 0b4eb93f5..9ec54d7a8 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt
@@ -39,11 +39,6 @@ import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.internal.AttributeImpl
import com.itsaky.androidide.inflater.internal.LayoutFile
import com.itsaky.androidide.inflater.internal.ViewImpl
-import com.itsaky.androidide.inflater.internal.utils.endParse
-import com.itsaky.androidide.inflater.internal.utils.parseId
-import com.itsaky.androidide.inflater.internal.utils.startParse
-import com.itsaky.androidide.projects.ProjectManager
-import com.itsaky.androidide.projects.api.AndroidModule
/**
* Attribute adapter for [View].
@@ -275,10 +270,6 @@ open class ViewAttrAdapter : IAttributeAdapter() {
@Suppress("UNCHECKED_CAST")
return (view.view as T).let {
val file = (view as ViewImpl).file
- val module =
- ProjectManager.findModuleForFile(file.file) as? AndroidModule
- ?: throw IllegalStateException("Cannot find module for file: $file")
- startParse(module)
val applied =
it.block(
file,
@@ -288,7 +279,6 @@ open class ViewAttrAdapter : IAttributeAdapter() {
attribute.name,
attribute.value
)
- endParse()
return@let applied
}
}
@@ -312,7 +302,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
protected open fun parsePorterDuffMode(mode: String): PorterDuff.Mode {
return com.itsaky.androidide.inflater.internal.utils.parsePorterDuffMode(mode)
}
-
+
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
protected open fun isApi29(): Boolean {
return Build.VERSION.SDK_INT >= 29
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/basicParseUtils.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/basicParseUtils.kt
index 6263e49a3..8106dab16 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/basicParseUtils.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/basicParseUtils.kt
@@ -24,6 +24,7 @@ import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.util.TypedValue.complexToDimension
import android.view.Gravity
+import android.view.InflateException
import android.view.ViewGroup.LayoutParams
import androidx.core.text.isDigitsOnly
import com.android.SdkConstants.EXT_XML
@@ -35,6 +36,7 @@ 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.LAYOUT
import com.android.aaptcompiler.AaptResourceType.STRING
import com.android.aaptcompiler.ArrayResource
import com.android.aaptcompiler.AttributeResource
@@ -200,6 +202,24 @@ fun parseDrawable(context: Context, value: String, def: Drawable = unknownDrawab
return def
}
+fun parseLayoutReference(value: String): File? {
+ if (value[0] != '@') {
+ throw InflateException("Value must be a reference to a layout file")
+ }
+
+ val layoutResolver: (Value?) -> File? =
+ fun(it): File? {
+ return if (it is FileReference) {
+ File(it.source.path)
+ } else null
+ }
+ val type = parseResourceReference(value)?.second ?: return null
+ if (type != LAYOUT) {
+ throwInvalidResType(LAYOUT, value)
+ }
+ return parseReference(value, type, null, layoutResolver)
+}
+
@JvmOverloads
fun parseColorDrawable(context: Context, value: String, def: Int = Color.TRANSPARENT): Drawable {
return newColorDrawable(parseColor(context, value, def))
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/module.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/module.kt
index b5fd8469e..df8042535 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/module.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/module.kt
@@ -26,11 +26,6 @@ val module: AndroidModule
get() =
currentModule ?: throw IllegalStateException("You must call startParse(AndroidModule) first")
-fun startParseIfNeeded(file: File) {
- if (currentModule != null) return
- (ProjectManager.findModuleForFile(file) as? AndroidModule)?.also { startParse(it) }
-}
-
fun startParse(m: AndroidModule) {
currentModule = m
}
diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt
index cd6ca79cf..03ea27efe 100644
--- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt
+++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt
@@ -44,8 +44,9 @@ class LayoutInflaterTest {
val inflater = ILayoutInflater.newInflater()
val inflated = inflater.inflate(file, parent)
inflated.apply {
- assertThat(this).isNotNull()
- assertThat(this!!.view).isInstanceOf(View::class.java)
+ assertThat(this).isNotEmpty()
+ assertThat(this).hasSize(1)
+ forEach { assertThat(it.view).isInstanceOf(View::class.java) }
}
}
}
@@ -62,10 +63,9 @@ class LayoutInflaterTest {
parent.removeAllViews()
module.createLayoutFile(view.simpleName!!) { file ->
file.writeText(viewDeclTemplate(view.simpleName!!))
- val inflated = inflater.inflate(file, parent)
- assertThat(inflated!!.view).isInstanceOf(view.java)
- assertThat(inflated.view.id)
- .isEqualTo(IDTable.get(view.simpleName!!, "template_view"))
+ val inflated = inflater.inflate(file, parent)[0]
+ assertThat(inflated.view).isInstanceOf(view.java)
+ assertThat(inflated.view.id).isEqualTo(IDTable.get(view.simpleName!!, "template_view"))
(inflated as ViewImpl).attributes.apply {
assertThat(this)
@@ -81,6 +81,45 @@ class LayoutInflaterTest {
}
}
+ @Test
+ fun `verify included view hierarchy`() {
+ inflaterTest { module ->
+ requiresActivity { activity ->
+ val parent = LinearLayout(activity)
+ val inflater = ILayoutInflater.newInflater()
+ val inflated = inflater.inflate(layoutFile("include"), parent)
+ assertThat(inflated).hasSize(1)
+
+ val view = inflated[0] as ViewImpl
+ assertThat(view.printHierarchy())
+ .isEqualTo(
+ "android.widget.LinearLayout\n" +
+ " android.widget.RelativeLayout\n" +
+ " android.widget.TextView\n" +
+ " android.widget.TextView\n"
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `verify merged view hierarchy`() {
+ inflaterTest { module ->
+ requiresActivity { activity ->
+ val parent = LinearLayout(activity)
+ val inflater = ILayoutInflater.newInflater()
+ val inflated = inflater.inflate(layoutFile("merge"), parent)
+ assertThat(inflated).hasSize(1)
+
+ val view = inflated[0] as ViewImpl
+ assertThat(view.printHierarchy())
+ .isEqualTo(
+ "android.widget.LinearLayout\n android.widget.TextView\n android.widget.TextView\n"
+ )
+ }
+ }
+ }
+
private fun layoutFile(name: String): File {
val app = ProjectManager.app ?: throw IllegalStateException("Project is not initialized")
return File(app.projectDir, "src/main/res/layout/$name.xml")
diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt
index 063707b0e..31a783e46 100644
--- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt
+++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ValueParsersTest.kt
@@ -19,13 +19,9 @@ 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
import com.itsaky.androidide.inflater.internal.utils.parseColor
import com.itsaky.androidide.inflater.internal.utils.parseDimension
@@ -34,7 +30,6 @@ import com.itsaky.androidide.inflater.internal.utils.parseInteger
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
@@ -47,7 +42,6 @@ class ValueParsersTest {
fun `hardcoded dimension parser test`() {
inflaterTest { module ->
requiresActivity { activity ->
- startParse(module)
// Hardcoded dimensions
val units = arrayOf("dp", "sp", "pt", "px", "in", "mm")
@@ -84,7 +78,6 @@ class ValueParsersTest {
assertThat(parseDimension(activity, "@dimen/test_dimen_1pt", def = 0f)).isEqualTo(1)
assertThat(parseDimension(activity, "@dimen/test_dimen_1pt_ref", def = 0f)).isEqualTo(1)
assertThat(parseDimension(activity, "@dimen/test_dimen_0pt_ref", def = 0f)).isEqualTo(0)
- endParse()
}
}
}
@@ -93,7 +86,6 @@ class ValueParsersTest {
fun `drawable parse test`() {
inflaterTest { module ->
requiresActivity { activity ->
- startParse(module)
parseDrawable(activity, "#ff0000").apply {
assertThat(this).isNotNull()
assertThat(this).isInstanceOf(ColorDrawable::class.java)
@@ -107,7 +99,6 @@ class ValueParsersTest {
assertThat(this).isNotNull()
assertThat(this).isInstanceOf(GradientDrawable::class.java)
}
- endParse()
}
}
}
@@ -116,7 +107,6 @@ class ValueParsersTest {
fun `boolean parser test`() {
inflaterTest { module ->
requiresActivity {
- startParse(module)
assertThat(parseBoolean("true")).isTrue()
assertThat(parseBoolean("false", def = true)).isFalse()
assertThat(parseBoolean("@android:bool/resolver_landscape_phone")).isTrue()
@@ -125,7 +115,6 @@ class ValueParsersTest {
assertThat(parseBoolean("@bool/test_bool_false", def = true)).isFalse()
assertThat(parseBoolean("@bool/test_bool_true_ref")).isTrue()
assertThat(parseBoolean("@bool/test_bool_false_ref", def = true)).isFalse()
- endParse()
}
}
}
@@ -134,7 +123,6 @@ class ValueParsersTest {
fun `integer parser test`() {
inflaterTest { module ->
requiresActivity {
- startParse(module)
assertThat(parseInteger("0", def = 1)).isEqualTo(0)
assertThat(parseInteger("10")).isEqualTo(10)
assertThat(parseInteger("110")).isEqualTo(110)
@@ -147,7 +135,6 @@ class ValueParsersTest {
assertThat(parseInteger("@integer/test_integer_1_ref")).isEqualTo(1)
assertThat(parseInteger("@integer/test_integer_0", def = 1)).isEqualTo(0)
assertThat(parseInteger("@integer/test_integer_0_ref", def = 1)).isEqualTo(0)
- endParse()
}
}
}
@@ -155,7 +142,6 @@ class ValueParsersTest {
@Test
fun `string parser test`() {
inflaterTest {
- startParse(it)
assertThat(parseString("Hello World!")).isEqualTo("Hello World!")
assertThat(parseString("@android:string/ok")).isEqualTo("OK")
assertThat(parseString("@android:string/cancel")).isEqualTo("Cancel")
@@ -163,34 +149,29 @@ class ValueParsersTest {
assertThat(parseString("@string/test_string_ref")).isEqualTo("I love Android!")
assertThat(parseString("@string/test_string_styled")).isEqualTo("I love Android!")
assertThat(parseString("@string/test_string_styled_ref")).isEqualTo("I love Android!")
- endParse()
}
}
@Test
fun `string array parser test`() {
inflaterTest {
- startParse(it)
assertThat(parseStringArray("@android:array/phoneTypes"))
.isEqualTo(
arrayOf("Home", "Mobile", "Work", "Work Fax", "Home Fax", "Pager", "Other", "Custom")
)
assertThat(parseStringArray("@array/test_string_array"))
.isEqualTo(arrayOf("I", "love", "Android"))
- endParse()
}
}
@Test
fun `integer array parser test`() {
inflaterTest {
- startParse(it)
assertThat(
parseIntegerArray("@android:array/config_defaultImperceptibleKillingExemptionProcStates")
)
.isEqualTo(intArrayOf(0, 1, 2, 4, 12))
assertThat(parseIntegerArray("@array/test_integer_array")).isEqualTo(intArrayOf(2, 4, 8))
- endParse()
}
}
@@ -198,7 +179,6 @@ class ValueParsersTest {
fun `color parser test`() {
inflaterTest {
requiresActivity { activity ->
- startParse(it)
assertThat(parseColor(activity, "@android:color/red")).isEqualTo(Color.RED)
assertThat(parseColor(activity, "@android:color/black")).isEqualTo(Color.BLACK)
assertThat(parseColor(activity, "@android:color/white")).isEqualTo(Color.WHITE)
@@ -209,7 +189,6 @@ class ValueParsersTest {
// TODO Implement color state list parser
assertThat(parseColor(activity, "@color/test_selector")).isEqualTo(Color.TRANSPARENT)
- endParse()
}
}
}
diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt
index 1fdf358c0..f98bfc682 100644
--- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt
+++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt
@@ -18,6 +18,8 @@
package com.itsaky.androidide.inflater
import androidx.appcompat.app.AppCompatActivity
+import com.itsaky.androidide.inflater.internal.utils.endParse
+import com.itsaky.androidide.inflater.internal.utils.startParse
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
@@ -51,7 +53,9 @@ object XmlInflaterTest {
fun inflaterTest(block: (AndroidModule) -> Unit) {
XmlInflaterTest.initIfNeeded()
+ startParse(ProjectManager.app!!)
block(ProjectManager.app!!)
+ endParse()
}
fun requiresActivity(block: (AppCompatActivity) -> Unit) {