From 59f585addd0f64d70bdb6bfbd3c428dd9d5bf821 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Sun, 6 Nov 2022 20:02:07 +0530 Subject: [PATCH] Implemented basic layout params parsers --- .../androidide/inflater/AbstractParser.kt | 10 ++ .../androidide/inflater/IAttributeAdapter.kt | 3 +- .../itsaky/androidide/inflater/INamespace.kt | 9 + .../inflater/internal/NamespaceImpl.kt | 22 +++ .../androidide/inflater/internal/ViewImpl.kt | 2 - .../internal/adapters/ViewAttrAdapter.kt | 165 ++++++++++++++++-- .../internal/adapters/ViewGroupAttrAdapter.kt | 3 +- 7 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/NamespaceImpl.kt 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") } }