diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar
index 16c463d6d..530501113 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
new file mode 100644
index 000000000..3c92d4f98
--- /dev/null
+++ b/tests/test-project/app/src/main/res/layout/include.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/tests/test-project/app/src/main/res/layout/inlined.xml b/tests/test-project/app/src/main/res/layout/inlined.xml
new file mode 100644
index 000000000..4d8727910
--- /dev/null
+++ b/tests/test-project/app/src/main/res/layout/inlined.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 000000000..1b75662a9
--- /dev/null
+++ b/tests/test-project/app/src/main/res/layout/layout_include.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/tests/test-project/app/src/main/res/layout/singleView.xml b/tests/test-project/app/src/main/res/layout/singleView.xml
index ebd55776d..798ae91d9 100644
--- a/tests/test-project/app/src/main/res/layout/singleView.xml
+++ b/tests/test-project/app/src/main/res/layout/singleView.xml
@@ -1,4 +1,5 @@
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ErrorView.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ErrorView.kt
index 16cf2d8d8..919a769a0 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ErrorView.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ErrorView.kt
@@ -23,6 +23,7 @@ import android.graphics.drawable.ColorDrawable
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
+import com.android.aaptcompiler.XmlResource
import com.itsaky.androidide.inflater.R
import java.io.File
@@ -31,7 +32,7 @@ import java.io.File
*
* @author Akash Yadav
*/
-class ErrorView(file: File, context: Context, message: String) :
+class ErrorView(file: LayoutFile, context: Context, message: String) :
ViewImpl(file = file, name = NAME, view = createErrView(context, message)) {
companion object {
private val NAME = View::class.java.name
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutFile.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutFile.kt
new file mode 100644
index 000000000..f0d4b7852
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutFile.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 .
+ */
+
+package com.itsaky.androidide.inflater.internal
+
+import java.io.File
+
+/**
+ * A data class to hold information about the file which was used to inflate and create a [ViewImpl]
+ * instance.
+ *
+ * @property file The actual file.
+ * @property resName The resource name.
+ * @author Akash Yadav
+ */
+data class LayoutFile(val file: File, val resName: String)
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 777467ea4..cfc797d2c 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
@@ -19,12 +19,12 @@ package com.itsaky.androidide.inflater.internal
import android.content.Context
import android.view.View
-import com.itsaky.androidide.inflater.InflateException
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import com.android.aapt.Resources.SourcePosition
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
@@ -36,8 +36,10 @@ import com.itsaky.androidide.inflater.IInflateEventsListener
import com.itsaky.androidide.inflater.ILayoutInflater
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
+import com.itsaky.androidide.inflater.InflateException
import com.itsaky.androidide.inflater.InflationFinishEvent
import com.itsaky.androidide.inflater.InflationStartEvent
+import com.itsaky.androidide.inflater.internal.utils.IDTable
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.utils.ILogger
@@ -55,17 +57,22 @@ open class LayoutInflaterImpl : ILayoutInflater() {
override var inflationEventListener: IInflateEventsListener? = null
private val log = ILogger.newInstance("LayoutInflaterImpl")
- private var inflatingFile: File? = null
+ private var _primaryInflatingFile: File? = null
+ private var _currentLayoutFile: LayoutFile? = null
+
+ protected val primaryInflatingFile: File
+ get() = this._primaryInflatingFile!!
- protected val file: File
- get() = this.inflatingFile!!
+ protected val currentLayoutFile: LayoutFile
+ get() = _currentLayoutFile!!
override fun inflate(file: File, parent: ViewGroup): IView? {
- this.inflatingFile = file
+ this._primaryInflatingFile = file
+ IDTable.newRound()
inflationEventListener?.onEvent(InflationStartEvent())
return doInflate(file, wrap(parent)).apply {
inflationEventListener?.onEvent(InflationFinishEvent(this))
- inflatingFile = null
+ _primaryInflatingFile = null
}
}
@@ -90,7 +97,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
processor.process(resFile, file.inputStream())
-
+
return doInflate(processor, parent, module)
}
@@ -101,18 +108,29 @@ open class LayoutInflaterImpl : ILayoutInflater() {
): IView? {
// TODO(itsaky) : Add test for multiple view as root layout
// The inflater should fail in such cases
- val (_, node) =
+ val (file, node) =
processor.xmlResources.find { it.file == processor.primaryFile }
?: throw InflateException("Unable to find primary XML resource from XmlProcessor")
-
+
+ this._currentLayoutFile = LayoutFile(this.primaryInflatingFile, file.name.entry!!)
+
if (node.nodeCase != ELEMENT) {
throw InflateException(
"Found ${node.nodeCase} but $ELEMENT was expected at ${node.source.lineCol()}"
)
}
-
+
+ // Store all IDs
+ file.exportedSymbols.filter { it.name.type == ID }.forEach {
+ IDTable.set(currentLayoutFile.resName, it.name.entry!!, View.generateViewId())
+ }
+
val element = node.element
- return onCreateView(element, parent, module)
+ val view = onCreateView(element, parent, module)
+
+ this._currentLayoutFile = null
+
+ return view
}
protected open fun onCreateView(
@@ -195,7 +213,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
): IView {
return try {
val v = createViewInstance(widget, parent)
- return ViewImpl(file, widget.qualifiedName, v)
+ return ViewImpl(currentLayoutFile, widget.qualifiedName, v)
} catch (err: Throwable) {
onCreateUnsupportedView("Unable to create view for widget ${widget.qualifiedName}", parent)
}
@@ -233,11 +251,11 @@ open class LayoutInflaterImpl : ILayoutInflater() {
}
private fun onCreateUnsupportedView(message: String, parent: ViewGroup): IView {
- return ErrorView(file, parent.context, message)
+ return ErrorView(currentLayoutFile, parent.context, message)
}
private fun wrap(parent: ViewGroup): IViewGroup {
- return ViewGroupImpl(file, parent.javaClass.name, parent)
+ return ViewGroupImpl(currentLayoutFile, parent.javaClass.name, parent)
}
private fun SourcePosition.lineCol(): String {
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 d3d3050dc..a62323ddd 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,11 +18,12 @@
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: File, name: String, view: ViewGroup) :
+open class ViewGroupImpl(file: LayoutFile, name: String, view: ViewGroup) :
ViewImpl(file = file, name = name, view = view), IViewGroup {
protected val children = mutableListOf()
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 4c4f7b255..eedb3432a 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
@@ -18,6 +18,7 @@
package com.itsaky.androidide.inflater.internal
import android.view.View
+import com.android.aaptcompiler.XmlResource
import com.itsaky.androidide.inflater.IAttribute
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
@@ -29,7 +30,7 @@ import java.io.File
open class ViewImpl
@JvmOverloads
constructor(
- internal val file: File,
+ internal val file: LayoutFile,
override val name: String,
override val view: View,
override val simpleName: String = name.simpleName(),
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 863221be0..cf5626b80 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
@@ -29,13 +29,14 @@ 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.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.parseString
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].
@@ -69,7 +70,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
"foregroundGravity" -> foregroundGravity = parseGravity(value)
"foregroundTint" -> foregroundTintList = parseColorStateList(context, value)
"foregroundTintMode" -> foregroundTintMode = parsePorterDuffMode(value)
- "id" -> {}
+ "id" -> parseId(file.resName, value)
"minHeight" -> minimumWidth = parseDimension(context, value)
"minWidth" -> minimumHeight = parseDimension(context, value)
"padding" ->
@@ -127,7 +128,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
attribute: IAttribute,
block:
T.(
- file: File,
+ file: LayoutFile,
context: Context,
layoutParams: ViewGroup.LayoutParams,
namespace: INamespace,
@@ -141,7 +142,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
(view.view as T).apply {
val file = (view as ViewImpl).file
val module =
- ProjectManager.findModuleForFile(file) as? AndroidModule
+ ProjectManager.findModuleForFile(file.file) as? AndroidModule
?: throw IllegalStateException("Cannot find module for file: $file")
startParse(module)
block(file, context, layoutParams, attribute.namespace, attribute.name, attribute.value)
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/IDTable.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/IDTable.kt
new file mode 100644
index 000000000..43f75df0e
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/IDTable.kt
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+ */
+
+package com.itsaky.androidide.inflater.internal.utils
+
+internal object IDTable {
+
+ private val ids = mutableMapOf>()
+
+ fun newRound() {
+ ids.clear()
+ }
+
+ fun get(file: String, name: String): Int? {
+ return ids[file]?.get(name)
+ }
+
+ fun set(file: String, name: String, id: Int) {
+ ids.computeIfAbsent(file) { mutableMapOf() }[name] = id
+ }
+}
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 7400d37fd..1a0e165df 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
@@ -440,7 +440,7 @@ fun resolveResourceReference(
}
}
-private fun parseResourceReference(value: String): Triple? {
+internal fun parseResourceReference(value: String): Triple? {
return tryParseReference(value)?.let {
val pck = it.reference.name.pck
val type = it.reference.name.type
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/viewParseUtils.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/viewParseUtils.kt
index d51c650c6..8fb287b1b 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/viewParseUtils.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/viewParseUtils.kt
@@ -19,9 +19,13 @@ package com.itsaky.androidide.inflater.internal.utils
import android.graphics.PorterDuff
import android.view.View
+import com.android.aaptcompiler.AaptResourceType.ID
+import com.itsaky.androidide.inflater.internal.ViewImpl
-fun parseId(value: String) : Int {
- return 0
+@JvmOverloads
+fun parseId(resName: String, value: String, def: Int = 0): Int {
+ val name = parseResourceReference(value)?.third ?: return 0
+ return IDTable.get(resName, name) ?: def
}
@Suppress("DEPRECATION")
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 1270c7d08..2ebd6e74f 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
@@ -53,6 +53,22 @@ class LayoutInflaterTest {
}
}
}
+
+ @Test
+ fun `test inlined layout`() {
+ inflaterTest {
+ requiresActivity { activity ->
+ val file = layoutFile("inlined")
+ val parent = LinearLayout(activity)
+ val inflater = ILayoutInflater.newInflater()
+ val inflated = inflater.inflate(file, parent)
+ inflated.apply {
+ assertThat(this).isNotNull()
+ assertThat(this).isInstanceOf(LinearLayout::class.java)
+ }
+ }
+ }
+ }
private fun layoutFile(name: String): File {
val app = ProjectManager.app ?: throw IllegalStateException("Project is not initialized")