diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/AbstractParser.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/AbstractParser.kt
index 267b382bb..2b69862ca 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/AbstractParser.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/AbstractParser.kt
@@ -66,6 +66,16 @@ abstract class AbstractParser {
return com.itsaky.androidide.inflater.internal.utils.parseBoolean(value = value, def = def)
}
+ /**
+ * Parses the given string value representing a string or reference to an string resource to its
+ * actual value. Returns [value] itself if it cannot be parsed.
+ *
+ * @param value The string value. Usually value from attributes.
+ */
+ protected open fun parseString(value: String): String {
+ return com.itsaky.androidide.inflater.internal.utils.parseString(value)
+ }
+
/**
* Parses the given string value representing a color or reference to a drawable resource to an
* actual drawable which can be rendered.
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt
index f57fb383f..34b8cb163 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt
@@ -29,8 +29,9 @@ abstract class IAttributeAdapter : AbstractParser() {
*
* @param view The view to which the attribute must be applied.
* @param attribute The attribute to apply.
+ * @return Whether the attribute was applied or not.
*/
- abstract fun apply(view: IView, attribute: IAttribute)
+ abstract fun apply(view: IView, attribute: IAttribute): Boolean
/**
* Apply the basic attributes to a view so that it could be rendered.
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt
index 48b537f79..b955f3240 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt
@@ -17,6 +17,10 @@
package com.itsaky.androidide.inflater
+import com.android.SdkConstants.ANDROID_NS_NAME
+import com.android.SdkConstants.ANDROID_URI
+import com.itsaky.androidide.inflater.internal.NamespaceImpl
+
/**
* An XML namespace declaration.
*
@@ -29,4 +33,9 @@ interface INamespace {
/** The namespace URI. */
val uri: String
+
+ companion object {
+ /** The Android namespace. */
+ @JvmField val ANDROID: INamespace = NamespaceImpl(ANDROID_NS_NAME, ANDROID_URI)
+ }
}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/NamespaceImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/NamespaceImpl.kt
new file mode 100644
index 000000000..ce5104e67
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/NamespaceImpl.kt
@@ -0,0 +1,22 @@
+/*
+ * 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 com.itsaky.androidide.inflater.INamespace
+
+data class NamespaceImpl(override val prefix: String, override val uri: String) : INamespace
\ No newline at end of file
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 eedb3432a..6bfcbafaa 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,14 +18,12 @@
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
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
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 cf5626b80..e6d0baf9b 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
@@ -21,6 +21,10 @@ import android.content.Context
import android.graphics.PorterDuff
import android.view.View
import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
import androidx.core.view.updatePadding
import androidx.core.view.updatePaddingRelative
import com.android.SdkConstants
@@ -33,7 +37,6 @@ 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
@@ -46,9 +49,10 @@ import com.itsaky.androidide.projects.api.AndroidModule
@AttributeAdapter(forView = View::class)
open class ViewAttrAdapter : IAttributeAdapter() {
- override fun apply(view: IView, attribute: IAttribute) {
- doApply(view, attribute) { file, context, layoutParams, namespace, name, value ->
- var handled = true
+ override fun apply(view: IView, attribute: IAttribute): Boolean {
+ return doApply(view, attribute) { file, context, layoutParams, _, name, value ->
+ var applied = true
+ @Suppress("DEPRECATION")
when (name) {
"layout_height" -> layoutParams.height = parseDimension(context, value)
"layout_width" -> layoutParams.width = parseDimension(context, value)
@@ -99,12 +103,30 @@ open class ViewAttrAdapter : IAttributeAdapter() {
"translationY" -> translationY = parseFloat(value)
"translationZ" -> translationZ = parseFloat(value)
"visibility" -> visibility = parseVisibility(value)
- else -> handled = false
+ else -> applied = false
}
- if (handled) {
+ if (!applied && layoutParams is LinearLayout.LayoutParams) {
+ applied = applyLinearLayoutParams(layoutParams, name, value)
+ }
+
+ if (!applied && layoutParams is RelativeLayout.LayoutParams) {
+ applied = applyRelativeLayoutParams(layoutParams, file.resName, name, value)
+ }
+
+ if (!applied && layoutParams is FrameLayout.LayoutParams) {
+ applied = applyFrameLayoutParams(layoutParams, name, value)
+ }
+
+ if (!applied && layoutParams is MarginLayoutParams) {
+ applied = applyMarginParams(context, layoutParams, name, value)
+ }
+
+ if (applied) {
this.layoutParams = layoutParams
}
+
+ return@doApply applied
}
}
@@ -112,6 +134,103 @@ open class ViewAttrAdapter : IAttributeAdapter() {
TODO("Not yet implemented")
}
+ protected open fun applyMarginParams(
+ context: Context,
+ params: MarginLayoutParams,
+ name: String,
+ value: String
+ ): Boolean {
+ var handled = true
+ when (name) {
+ "layout_margin" -> {
+ val margin = parseDimension(context, value, 0)
+ params.setMargins(margin, margin, margin, margin)
+ }
+ "layout_marginLeft" -> params.leftMargin = parseDimension(context, value)
+ "layout_marginTop" -> params.topMargin = parseDimension(context, value)
+ "layout_marginRight" -> params.rightMargin = parseDimension(context, value)
+ "layout_marginBottom" -> params.bottomMargin = parseDimension(context, value)
+ "layout_marginStart" -> params.marginStart = parseDimension(context, value)
+ "layout_marginEnd" -> params.marginEnd = parseDimension(context, value)
+ else -> handled = false
+ }
+ return handled
+ }
+
+ protected open fun applyFrameLayoutParams(
+ params: FrameLayout.LayoutParams,
+ name: String,
+ value: String,
+ ): Boolean {
+ var applied = true
+ if ("layout_gravity" == name) {
+ params.gravity = parseGravity(value)
+ } else {
+ applied = false
+ }
+ return applied
+ }
+
+ protected open fun applyRelativeLayoutParams(
+ params: RelativeLayout.LayoutParams,
+ resName: String,
+ name: String,
+ value: String,
+ ): Boolean {
+ var handled = true
+ when (name) {
+ "layout_above" -> params.addRule(RelativeLayout.ABOVE, parseId(resName, value))
+ "layout_alignBaseline" ->
+ params.addRule(RelativeLayout.ALIGN_BASELINE, parseId(resName, value))
+ "layout_alignBottom" -> params.addRule(RelativeLayout.ALIGN_BOTTOM, parseId(resName, value))
+ "layout_alignEnd" -> params.addRule(RelativeLayout.ALIGN_END, parseId(resName, value))
+ "layout_alignLeft" -> params.addRule(RelativeLayout.ALIGN_LEFT, parseId(resName, value))
+ "layout_alignParentTop" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_TOP, params)
+ "layout_alignParentBottom" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_BOTTOM, params)
+ "layout_alignParentStart" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_START, params)
+ "layout_alignParentEnd" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_END, params)
+ "layout_alignParentLeft" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_LEFT, params)
+ "layout_alignParentRight" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.ALIGN_PARENT_RIGHT, params)
+ "layout_alignRight" -> params.addRule(RelativeLayout.ALIGN_RIGHT, parseId(resName, value))
+ "layout_alignStart" -> params.addRule(RelativeLayout.ALIGN_START, parseId(resName, value))
+ "layout_alignTop" -> params.addRule(RelativeLayout.ALIGN_TOP, parseId(resName, value))
+ "layout_alignWithParentIfMissing" -> params.alignWithParent = parseBoolean(value)
+ "layout_below" -> params.addRule(RelativeLayout.BELOW, parseId(resName, value))
+ "layout_centerHorizontal" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.CENTER_HORIZONTAL, params)
+ "layout_centerInParent" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.CENTER_IN_PARENT, params)
+ "layout_centerVertical" ->
+ setRelativeRuleIfTrue(parseBoolean(value), RelativeLayout.CENTER_VERTICAL, params)
+ "layout_toEndOf" -> params.addRule(RelativeLayout.END_OF, parseId(resName, value))
+ "layout_toStartOf" -> params.addRule(RelativeLayout.START_OF, parseId(resName, value))
+ "layout_toLeftOf" -> params.addRule(RelativeLayout.LEFT_OF, parseId(resName, value))
+ "layout_toRightOf" -> params.addRule(RelativeLayout.RIGHT_OF, parseId(resName, value))
+ else -> handled = false
+ }
+ return handled
+ }
+
+ protected open fun applyLinearLayoutParams(
+ params: LinearLayout.LayoutParams,
+ name: String,
+ value: String,
+ ): Boolean {
+ var applied = true
+ when (name) {
+ "layout_gravity" -> params.gravity = parseGravity(value)
+ "layout_weight" -> params.weight = parseFloat(value, 1f)
+ else -> applied = false
+ }
+ return applied
+ }
+
protected open fun canHandleNamespace(namespace: INamespace): Boolean {
return this.canHandleNamespace(namespace.uri)
}
@@ -133,20 +252,30 @@ open class ViewAttrAdapter : IAttributeAdapter() {
layoutParams: ViewGroup.LayoutParams,
namespace: INamespace,
name: String,
- value: String
- ) -> Unit
- ) {
+ value: String,
+ ) -> Boolean,
+ ): Boolean {
if (!canHandleNamespace(attribute.namespace)) {
- return
+ return false
}
- (view.view as T).apply {
+ @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)
- block(file, context, layoutParams, attribute.namespace, attribute.name, attribute.value)
+ val applied =
+ it.block(
+ file,
+ it.context,
+ it.layoutParams,
+ attribute.namespace,
+ attribute.name,
+ attribute.value
+ )
endParse()
+ return@let applied
}
}
@@ -169,4 +298,16 @@ open class ViewAttrAdapter : IAttributeAdapter() {
protected open fun parsePorterDuffMode(mode: String): PorterDuff.Mode {
return com.itsaky.androidide.inflater.internal.utils.parsePorterDuffMode(mode)
}
+
+ private fun setRelativeRuleIfTrue(
+ condition: Boolean,
+ rule: Int,
+ params: RelativeLayout.LayoutParams,
+ ) {
+ if (condition) {
+ params.addRule(rule)
+ } else {
+ params.removeRule(rule)
+ }
+ }
}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt
index 03a69f171..e8731f20a 100644
--- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt
@@ -20,14 +20,13 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.view.ViewGroup
import com.itsaky.androidide.annotations.inflater.AttributeAdapter
import com.itsaky.androidide.inflater.IAttribute
-import com.itsaky.androidide.inflater.IAttributeAdapter
import com.itsaky.androidide.inflater.IView
/** @author Akash Yadav */
@AttributeAdapter(ViewGroup::class)
class ViewGroupAttrAdapter : ViewAttrAdapter() {
- override fun apply(view: IView, attribute: IAttribute) {
+ override fun apply(view: IView, attribute: IAttribute): Boolean {
TODO("Not yet implemented")
}
}