From c8fd786cfdf91233fe6aa0b3a7eac6ad1029339e Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Sat, 1 Jan 2022 17:44:40 +0530 Subject: [PATCH] Implement ShapeDrawableParser --- .idea/misc.xml | 1 + .../itsaky/inflater/XMLLayoutInflater.java | 271 +++++++------- .../android/view/ViewAttrAdapter.java | 189 ++-------- .../android/view/ViewGroupAttrAdapter.java | 6 + .../android/widget/AbsSeekBarAttrAdapter.java | 8 +- .../android/widget/AbsSpinnerAttrAdapter.java | 12 +- .../widget/AdapterViewAttrAdapter.java | 10 +- .../android/widget/ButtonAttrAdapter.java | 10 +- .../android/widget/CheckBoxAttrAdapter.java | 10 +- .../widget/CheckedTextViewAttrAdapter.java | 10 +- .../widget/CompondButtonAttrAdapter.java | 10 +- .../android/widget/EditTextAttrAdapter.java | 8 + .../widget/ImageButtonAttrAdapter.java | 8 + .../android/widget/ImageViewAttrAdapter.java | 9 +- .../widget/LinearLayoutAttrAdapter.java | 8 +- .../widget/ProgressBarAttrAdapter.java | 10 +- .../widget/RadioButtonAttrAdapter.java | 10 +- .../widget/RelativeLayoutAttrAdapter.java | 11 +- .../android/widget/SeekBarAttrAdapter.java | 8 +- .../android/widget/SpinnerAttrAdapter.java | 8 +- .../android/widget/TextViewAttrAdapter.java | 7 +- .../drawable/DrawableParserFactory.java | 142 +++++++ .../inflater/drawable/IDrawableParser.java | 74 ++++ .../drawable/ShapeDrawableParser.java | 349 ++++++++++++++++++ .../inflater/util/CommonParseUtils.java | 235 ++++++++++++ 25 files changed, 1110 insertions(+), 314 deletions(-) create mode 100644 xml-inflater/src/main/java/com/itsaky/inflater/drawable/DrawableParserFactory.java create mode 100644 xml-inflater/src/main/java/com/itsaky/inflater/drawable/IDrawableParser.java create mode 100644 xml-inflater/src/main/java/com/itsaky/inflater/drawable/ShapeDrawableParser.java create mode 100644 xml-inflater/src/main/java/com/itsaky/inflater/util/CommonParseUtils.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 6412c4d90..53dc4cb34 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -14,6 +14,7 @@ + diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/XMLLayoutInflater.java b/xml-inflater/src/main/java/com/itsaky/inflater/XMLLayoutInflater.java index 947ec8de4..6031f59bb 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/XMLLayoutInflater.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/XMLLayoutInflater.java @@ -1,29 +1,32 @@ /************************************************************************************ * 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.inflater; import android.content.Context; +import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; + import com.itsaky.androidide.app.BaseApplication; import com.itsaky.androidide.ui.util.Preconditions; import com.itsaky.androidide.utils.Logger; @@ -36,11 +39,13 @@ import com.itsaky.inflater.impl.UiView; import com.itsaky.inflater.impl.UiViewGroup; import com.itsaky.widgets.WidgetInfo; import com.itsaky.widgets.models.Widget; + import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Set; import java.util.regex.Pattern; + import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Attributes; @@ -59,60 +64,60 @@ class XMLLayoutInflater extends BaseLayoutInflater { private ContextProvider contextProvider; - private static final Logger LOG = Logger.instance("XMLLayoutInflater"); + private static final Logger LOG = Logger.instance ("XMLLayoutInflater"); public static final String ATTR_ADAPTER_SUFFIX = "AttrAdapter"; private XMLLayoutInflater () { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException (); } - XMLLayoutInflater(@NonNull LayoutInflaterConfiguration config) { + XMLLayoutInflater (@NonNull LayoutInflaterConfiguration config) { this.resDirs = config.resDirs; this.attrInfo = config.attrInfo; this.widgetInfo = config.widgetInfo; this.resFinder = config.resFinder; this.contextProvider = config.contextProvider; - Preconditions.assertAllNotNull("LayoutInflater parameters cannot be null", resDirs, attrInfo, widgetInfo, resFinder); + Preconditions.assertAllNotNull ("LayoutInflater parameters cannot be null", resDirs, attrInfo, widgetInfo, resFinder); } - + @Override @NonNull - protected IView doInflate(String layout, ViewGroup parent) throws InflateException { + protected IView doInflate (String layout, ViewGroup parent) throws InflateException { try { - IDTable.newRound(); + IDTable.newRound (); // Notify - preInflate(); + preInflate (); - final Document doc = Jsoup.parse(layout, "", Parser.xmlParser()); + final Document doc = Jsoup.parse (layout, "", Parser.xmlParser ()); - if (doc.childrenSize() <= 0) { + if (doc.childrenSize () <= 0) { throw new InflateException ("No views added"); } - if (doc.childrenSize() > 1) { + if (doc.childrenSize () > 1) { throw new InflateException ("More than one root element was found. An XML layout can have only one root element."); } - IView root = onCreateView(doc.child(0), parent); + IView root = onCreateView (doc.child (0), parent); if (root == null) { - root = onCreateErrorView(TextView.class.getName(), "No views added"); + root = onCreateErrorView (TextView.class.getName (), "No views added"); } // Notify - postInflate(root); + postInflate (root); return root; } catch (Throwable th) { - throw new InflateException("Unable to inflate layout", th); + throw new InflateException ("Unable to inflate layout", th); } } - + @Override - public void resetContextProvider(ILayoutInflater.ContextProvider provider) { + public void resetContextProvider (ILayoutInflater.ContextProvider provider) { this.contextProvider = provider; } @@ -127,203 +132,205 @@ class XMLLayoutInflater extends BaseLayoutInflater { return null; } - final String name = tag.tagName().trim(); - - if (name.equals("include")) { - return createFromInclude (tag.attributes(), parent); + final String name = tag.tagName ().trim (); + + if (name.equals ("include")) { + return createFromInclude (tag.attributes (), parent); } - + IView root = null; - int style = tag.hasAttr("style") ? parseFrameworkStyle (tag.attr("style")) : 0; - if (!name.contains(".")) { + int style = tag.hasAttr ("style") ? parseFrameworkStyle (tag.attr ("style")) : 0; + if (!name.contains (".")) { root = createFromSimpleName (name, parent, style); } else { root = createFromQualifiedName (name, parent, style); } - + if (root == null) { return null; } registerAttributeAdaptersTo (root); - - if (!tag.attributes().isEmpty()) { - addAttributesTo(root, tag.attributes()); + + if (!tag.attributes ().isEmpty ()) { + addAttributesTo (root, tag.attributes ()); } - + if (root instanceof IViewGroup) { addChildren (tag, (IViewGroup) root, parent); } - postCreateView(root); - + postCreateView (root); + return root; } - - protected void addChildren(@NonNull Element tag, IViewGroup root, ViewGroup parent) { - if (tag.childrenSize() > 0 && !root.isPlaceholder()) { - for (Element child : tag.children()) { - final BaseView created = (BaseView) onCreateView(child, (ViewGroup) root.asView()); - if(created != null) { - root.addView(created); - created.setParent(root); + + protected void addChildren (@NonNull Element tag, IViewGroup root, ViewGroup parent) { + if (tag.childrenSize () > 0 && !root.isPlaceholder ()) { + for (Element child : tag.children ()) { + final BaseView created = (BaseView) onCreateView (child, (ViewGroup) root.asView ()); + if (created != null) { + root.addView (created); + created.setParent (root); } } } } - - protected int parseFrameworkStyle (String value) throws InflateException{ - + + protected int parseFrameworkStyle (String value) throws InflateException { + try { - if (value.startsWith("?android:attr/")) { - final String name = underscorize (value.substring("?android:attr/".length())); - return (int) android.R.attr.class.getField(name).get(null); - } else if (value.startsWith("@android:style/")) { - final String name = underscorize (value.substring("@android:style/".length())); - return (int) android.R.style.class.getField(name).get(null); + if (value.startsWith ("?android:attr/")) { + final String name = underscorize (value.substring ("?android:attr/".length ())); + return (int) android.R.attr.class.getField (name).get (null); + } else if (value.startsWith ("@android:style/")) { + final String name = underscorize (value.substring ("@android:style/".length ())); + return (int) android.R.style.class.getField (name).get (null); } } catch (Throwable th) { throw new InflateException (th); } - + return 0; } - + @NonNull - private String underscorize(@NonNull String substring) { - return substring.replace(".", "_"); - } - - protected void registerAttributeAdaptersTo(@NonNull IView root) { - root.registerAttributeAdapter(onCreateAttributeAdapter (root.asView())); + private String underscorize (@NonNull String substring) { + return substring.replace (".", "_"); } - protected IAttributeAdapter onCreateAttributeAdapter(View view) { + protected void registerAttributeAdaptersTo (@NonNull IView root) { + root.registerAttributeAdapter (onCreateAttributeAdapter (root.asView ())); + } + + protected IAttributeAdapter onCreateAttributeAdapter (View view) { + final var displayMetrics = contextProvider.getContext ().getResources ().getDisplayMetrics (); try { - final String adapterName = view.getClass().getName().concat(ATTR_ADAPTER_SUFFIX); - final String name = "com.itsaky.inflater.adapters.".concat(adapterName); - final Class adapterClass = getClass() - .getClassLoader() - .loadClass(name) - .asSubclass(IAttributeAdapter.class); - final var adapter = adapterClass.newInstance(); - adapter.setResourceFinder (resFinder); - return adapter; + final String adapterName = view.getClass ().getName ().concat (ATTR_ADAPTER_SUFFIX); + final String name = "com.itsaky.inflater.adapters.".concat (adapterName); + final Class adapterClass = getClass () + .getClassLoader () + .loadClass (name) + .asSubclass (IAttributeAdapter.class); + final var constructor = adapterClass.getConstructor (IResourceFinder.class, DisplayMetrics.class); + final var adapter = constructor.newInstance (resFinder, displayMetrics); + adapter.setResourceFinder (resFinder); + return adapter; } catch (Throwable th) { - LOG.error (BaseApplication.getBaseInstance().getString(com.itsaky.inflater.R.string.err_no_attr_adapter, view.getClass().getName()), th); + LOG.error (BaseApplication.getBaseInstance ().getString (com.itsaky.inflater.R.string.err_no_attr_adapter, view.getClass ().getName ()), th); } // If we cannot find a suitable adapter, fall back to using common adapters IAttributeAdapter adapter = null; if (view instanceof ViewGroup) { - adapter = new ViewGroupAttrAdapter (); + adapter = new ViewGroupAttrAdapter (resFinder, displayMetrics); } else { - adapter = new ViewAttrAdapter (); + adapter = new ViewAttrAdapter (resFinder, displayMetrics); } return adapter; } - - protected void addAttributesTo(IView view, Attributes attributes) { - assertNotnull(attributes, "Cannot apply null attributes!"); - + + protected void addAttributesTo (IView view, Attributes attributes) { + assertNotnull (attributes, "Cannot apply null attributes!"); + for (Attribute attr : attributes) { - final String key = attr.getKey(); - final String[] split = !key.contains(":") ? new String[] {"", key} : key.split(Pattern.quote(":"), 2); + final String key = attr.getKey (); + final String[] split = !key.contains (":") ? new String[]{"", key} : key.split (Pattern.quote (":"), 2); final String namespace = split[0]; final String name = split[1]; - final String value = attr.getValue(); - + final String value = attr.getValue (); + final IAttribute iAttr = asAttribute (namespace, name, value); - view.addAttribute(iAttr); - - postApplyAttribute(iAttr, view); + view.addAttribute (iAttr); + + postApplyAttribute (iAttr, view); } } - - protected IAttribute asAttribute(String namespace, String name, String value) { + + protected IAttribute asAttribute (String namespace, String name, String value) { return new UiAttribute (namespace, name, value); } - + protected IView create (@NonNull String name, ViewGroup parent, int style) throws InflateException { - if (!name.contains(".")) { + if (!name.contains (".")) { return createFromSimpleName (name, parent, style); } else { return createFromQualifiedName (name, parent, style); } } - + protected IView createFromInclude (Attributes attrs, ViewGroup parent) { throw new UnsupportedOperationException ("Inflating from is not supported yet!"); } - - protected IView createFromSimpleName(String name, ViewGroup parent, int style) throws InflateException { - final Widget widget = widgetInfo.getWidgetBySimpleName(name); + + protected IView createFromSimpleName (String name, ViewGroup parent, int style) throws InflateException { + final Widget widget = widgetInfo.getWidgetBySimpleName (name); if (widget == null) { - LOG.error("Unable to inflate view. widget == null"); - return onCreateErrorView(name, getString(com.itsaky.inflater.R.string.msg_cannot_create_view, name)); + LOG.error ("Unable to inflate view. widget == null"); + return onCreateErrorView (name, getString (com.itsaky.inflater.R.string.msg_cannot_create_view, name)); } - - return createFromQualifiedName(widget.name, parent, style); + + return createFromQualifiedName (widget.name, parent, style); } - - protected IView createFromQualifiedName(String name, ViewGroup parent, int style) throws InflateException { - assertNotBlank(name, "Invalid tag name: " + name); + + protected IView createFromQualifiedName (String name, ViewGroup parent, int style) throws InflateException { + assertNotBlank (name, "Invalid tag name: " + name); // TODO Try to load classes directly from .class files if possible try { - final View created = createAndroidViewForName(name); + final View created = createAndroidViewForName (name); final BaseView view = - created instanceof ViewGroup - ? new UiViewGroup (name, (ViewGroup) created) - : new UiView (name, created); - return applyLayoutParams(view, parent); + created instanceof ViewGroup + ? new UiViewGroup (name, (ViewGroup) created) + : new UiView (name, created); + return applyLayoutParams (view, parent); } catch (Throwable th) { - final var msg = getString(R.string.msg_cannot_create_view, name); - LOG.error(msg, th); - return onCreateErrorView(name, msg); + final var msg = getString (R.string.msg_cannot_create_view, name); + LOG.error (msg, th); + return onCreateErrorView (name, msg); } } - + @NonNull - protected View createAndroidViewForName(String name) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, java.lang.reflect.InvocationTargetException { - final Class loaded = Class.forName(name).asSubclass(View.class); - final Constructor constructor = loaded.getConstructor(Context.class /*, AttributeSet.class, int.class*/); - final View created = constructor.newInstance(contextProvider.getContext()/*, null, style*/); + protected View createAndroidViewForName (String name) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, java.lang.reflect.InvocationTargetException { + final Class loaded = Class.forName (name).asSubclass (View.class); + final Constructor constructor = loaded.getConstructor (Context.class /*, AttributeSet.class, int.class*/); + final View created = constructor.newInstance (contextProvider.getContext ()/*, null, style*/); return created; } - - protected IView applyLayoutParams(@NonNull IView view, ViewGroup parent) { - final ViewGroup.LayoutParams params = generateLayoutParams(parent); - view.asView().setLayoutParams(params); + + protected IView applyLayoutParams (@NonNull IView view, ViewGroup parent) { + final ViewGroup.LayoutParams params = generateLayoutParams (parent); + view.asView ().setLayoutParams (params); return view; } - + protected IView onCreateErrorView (String name, String msg) throws InflateException { - final Context ctx = contextProvider.getContext(); - assertNotnull(ctx, "Context is null!"); - + final Context ctx = contextProvider.getContext (); + assertNotnull (ctx, "Context is null!"); + final TextView error = new TextView (ctx); - error.setText(msg); - error.setLayoutParams(new ViewGroup.LayoutParams (-2, -2)); - error.setTextColor(ContextCompat.getColor(ctx, android.R.color.black)); - error.setBackgroundColor(ContextCompat.getColor(ctx, android.R.color.white)); - + error.setText (msg); + error.setLayoutParams (new ViewGroup.LayoutParams (-2, -2)); + error.setTextColor (ContextCompat.getColor (ctx, android.R.color.black)); + error.setBackgroundColor (ContextCompat.getColor (ctx, android.R.color.white)); + return new UiView (name, error, true); } - + private String getString (@StringRes int id, Object... format) throws InflateException { - assertNotnull(contextProvider.getContext(), "Context is null!"); - return contextProvider.getContext().getString(id, format); + assertNotnull (contextProvider.getContext (), "Context is null!"); + return contextProvider.getContext ().getString (id, format); } @NonNull protected ViewGroup.LayoutParams generateLayoutParams (ViewGroup parent) { try { - final Class clazz = parent.getClass(); + final Class clazz = parent.getClass (); Method method = null; try { - method = clazz.getDeclaredMethod("generateDefaultLayoutParams"); + method = clazz.getDeclaredMethod ("generateDefaultLayoutParams"); } catch (NoSuchMethodException | SecurityException e) { // If the method is not implicitly declared in this ViewGroup, // Then try to get the method from its superclass @@ -333,8 +340,8 @@ class XMLLayoutInflater extends BaseLayoutInflater { // No null check here // If we reach here, then method is definitely not null - method.setAccessible(true); - return (ViewGroup.LayoutParams) method.invoke(parent); + method.setAccessible (true); + return (ViewGroup.LayoutParams) method.invoke (parent); } catch (Throwable th) { throw new InflateException ("Unable to create layout params for parent: " + parent, th); } diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewAttrAdapter.java index 529a6cb6b..a1b7bf2f3 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewAttrAdapter.java @@ -19,13 +19,7 @@ package com.itsaky.inflater.adapters.android.view; import android.content.Context; import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.NinePatch; import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; import android.os.Build; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -36,22 +30,15 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; -import com.blankj.utilcode.util.ImageUtils; import com.itsaky.androidide.utils.Logger; import com.itsaky.inflater.IAttribute; import com.itsaky.inflater.IAttributeAdapter; import com.itsaky.inflater.IDTable; import com.itsaky.inflater.IResourceFinder; -import com.sdsmdg.harjot.vectormaster.VectorMasterDrawable; +import com.itsaky.inflater.util.CommonParseUtils; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.lang.reflect.Field; import java.util.regex.Pattern; /** @@ -61,18 +48,17 @@ import java.util.regex.Pattern; * * @author Akash Yadav */ -public class ViewAttrAdapter implements IAttributeAdapter { - - private static final Pattern HEX_COLOR = Pattern.compile("#[a-fA-F0-9]{6,8}"); +public class ViewAttrAdapter extends CommonParseUtils implements IAttributeAdapter { protected static final Logger LOG = Logger.instance ("BaseViewAttrAdapter"); - public static final int ENUM_NOT_FOUND = -4116; - protected IResourceFinder resFinder; + public ViewAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } @Override public void setResourceFinder (IResourceFinder resourceFinder) { - this.resFinder = resourceFinder; + this.resourceFinder = resourceFinder; } @Override @@ -87,7 +73,6 @@ public class ViewAttrAdapter implements IAttributeAdapter { final String value = attribute.getValue(); final ViewGroup.LayoutParams params = view.getLayoutParams(); final Resources res = view.getResources(); - final DisplayMetrics dm = res.getDisplayMetrics(); final Context ctx = view.getContext(); if (!canHandleNamespace(namespace)) { @@ -97,10 +82,10 @@ public class ViewAttrAdapter implements IAttributeAdapter { boolean handled = true; switch (name) { case "layout_height" : - params.height = parseDimension (value, -2, dm); + params.height = parseDimension (value, -2, displayMetrics); break; case "layout_width" : - params.width = parseDimension (value, -2, dm); + params.width = parseDimension (value, -2, displayMetrics); break; case "alpha" : view.setAlpha(parseFloat(value)); @@ -135,13 +120,13 @@ public class ViewAttrAdapter implements IAttributeAdapter { view.setDuplicateParentStateEnabled(parseBoolean(value)); break; case "elevation" : - view.setElevation(parseDimension(value, 0, dm)); + view.setElevation(parseDimension(value, 0, displayMetrics)); break; case "fadeScrollbars" : view.setScrollbarFadingEnabled(parseBoolean(value)); break; case "fadingEdgeLength" : - view.setFadingEdgeLength(parseDimension(value, 0, dm)); + view.setFadingEdgeLength(parseDimension(value, 0, displayMetrics)); break; case "filterTouchesWhenObscured" : view.setFilterTouchesWhenObscured(parseBoolean(value)); @@ -162,37 +147,37 @@ public class ViewAttrAdapter implements IAttributeAdapter { view.setId(parseId (value)); break; case "minHeight" : - view.setMinimumHeight(parseDimension(value, 0, dm)); + view.setMinimumHeight(parseDimension(value, 0, displayMetrics)); break; case "minWidth" : - view.setMinimumWidth(parseDimension(value, 0, dm)); + view.setMinimumWidth(parseDimension(value, 0, displayMetrics)); break; case "padding" : - final int padding = parseDimension(value, 0, dm); + final int padding = parseDimension(value, 0, displayMetrics); view.setPaddingRelative(padding, padding, padding, padding); break; case "paddingLeft" : - final int paddingLeft = parseDimension(value, 0, dm); + final int paddingLeft = parseDimension(value, 0, displayMetrics); view.setPadding(paddingLeft, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); break; case "paddingTop" : - final int paddingTop = parseDimension(value, 0, dm); + final int paddingTop = parseDimension(value, 0, displayMetrics); view.setPadding(view.getPaddingLeft(), paddingTop, view.getPaddingRight(), view.getPaddingBottom()); break; case "paddingRight" : - final int paddingRight = parseDimension(value, 0, dm); + final int paddingRight = parseDimension(value, 0, displayMetrics); view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), paddingRight, view.getPaddingBottom()); break; case "paddingBottom" : - final int paddingBottom = parseDimension(value, 0, dm); + final int paddingBottom = parseDimension(value, 0, displayMetrics); view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), paddingBottom); break; case "paddingStart" : - final int paddingStart = parseDimension(value, 0, dm); + final int paddingStart = parseDimension(value, 0, displayMetrics); view.setPaddingRelative(paddingStart, view.getPaddingTop(), view.getPaddingEnd(), view.getPaddingBottom()); break; case "paddingEnd" : - final int paddingEnd = parseDimension(value, 0, dm); + final int paddingEnd = parseDimension(value, 0, displayMetrics); view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), paddingEnd, view.getPaddingBottom()); break; case "rotation" : @@ -253,7 +238,7 @@ public class ViewAttrAdapter implements IAttributeAdapter { // ----- Handle attributes related to parent ------ if (!handled && params instanceof LinearLayout.LayoutParams) { - handled = handleLinearLayoutParams ((LinearLayout.LayoutParams) params, name, value, dm); + handled = handleLinearLayoutParams ((LinearLayout.LayoutParams) params, name, value, displayMetrics); } if (!handled && params instanceof RelativeLayout.LayoutParams) { @@ -261,7 +246,7 @@ public class ViewAttrAdapter implements IAttributeAdapter { } if (!handled && params instanceof ViewGroup.MarginLayoutParams) { - handled = handleMarginParams((ViewGroup.MarginLayoutParams) params, name, value, dm); + handled = handleMarginParams((ViewGroup.MarginLayoutParams) params, name, value, displayMetrics); } view.setLayoutParams(params); @@ -535,24 +520,11 @@ public class ViewAttrAdapter implements IAttributeAdapter { protected String parseString(@NonNull String value) { if (value.startsWith("@")) { - return parseString(resFinder.findString(value.substring("@string/".length()))); + return parseString(resourceFinder.findString(value.substring("@string/".length()))); } return value; } - protected boolean parseBoolean(@NonNull String value) { - if (!value.startsWith("@")) { - if ("true".equals(value)) { - return true; - } else if ("false".equals(value)) { - return false; - } - } else { - // TODO Find resource value in booleans.xml - } - return false; - } - protected PorterDuff.Mode parsePorterDuffMode(@NonNull String mode) { switch (mode) { case "add" : @@ -572,121 +544,6 @@ public class ViewAttrAdapter implements IAttributeAdapter { } } - protected Drawable parseDrawable (String value, final Context ctx) { - if (HEX_COLOR.matcher(value).matches()) { - return drawableForColor(value); - } else if (value.startsWith("@")) { - - // First check if this is a reference to an android resource - if (value.startsWith("@android:")) { - final String typeAndValue = value.substring("@android:".length()); - final String[] split = typeAndValue.split(Pattern.quote("/")); // For @android:color/white, it will be ["color", "white"] - final String type = split[0]; - final String typeVal = split[1]; - final int id = findAndroidResId (type, typeVal); - - if (id != -1) { - switch (type) { - case "color" : - return drawableForColor(ContextCompat.getColor(ctx, id)); - case "drawable" : - return ContextCompat.getDrawable(ctx, id); - default : - return newTransparentDrawable(); - } - } - - } else { - // We found a reference to another resource - if (value.startsWith("@drawable/")) { - final File drawable = resFinder.findDrawable (value.substring("@drawable/".length())); - - if (drawable == null) { - return null; - } - - if (drawable.getName ().endsWith (".xml")) { - // TODO This might not always be a vector drawable - // Implement a drawable parser that can parse everything listed here: https://developer.android.com/guide/topics/resources/drawable-resource - try { - return VectorMasterDrawable.fromXMLFile (drawable); - } catch (XmlPullParserException e) { - LOG.error ("Failed to parse XML Drawable", e); - return null; - } - } else { - return parseImageDrawable (ctx, drawable); - } - } else if (value.startsWith("@color/")) { - final String color = resFinder.findColor(value.substring("@color/".length())); - // TODO Check if this color resource is a selector - return parseDrawable(color, ctx); - } - } - } - return newTransparentDrawable(); - } - - // NinePatch has not been tested - @Nullable - private Drawable parseImageDrawable (final Context context, final File file) { - final var bitmap = ImageUtils.getBitmap (file); - if (bitmap == null) { - return null; - } - - final var chunk = bitmap.getNinePatchChunk (); - if (NinePatch.isNinePatchChunk (chunk)) { - return new NinePatchDrawable (context.getResources (), new NinePatch (bitmap, chunk)); - } - - return new BitmapDrawable (context.getResources (), bitmap); - } - - private int findAndroidResId(String type, String name) { - try { - final Class typeClass = android.R.class.getClassLoader().loadClass("android.R$" + type); - final Field typeField = typeClass.getDeclaredField(name); - typeField.setAccessible(true); - return (int) typeField.get(null); - } catch (Throwable th) { - return -1; - } - } - - protected int parseColor (String color, final Context ctx) { - if (HEX_COLOR.matcher(color).matches()) { - try { - return Color.parseColor(color); - } catch (Throwable th) { - // Ignored - } - } else if (color.startsWith("@color/")) { - return parseColor(resFinder.findColor(color.substring("@color/".length())), ctx); - } else if (color.startsWith("@android:color/")) { - final int id = findAndroidResId("color", color.substring("@android:color/".length())); - return ContextCompat.getColor(ctx, id); - } - - return Color.parseColor("#00ffffff"); - } - - protected Drawable drawableForColor (String color) { - try { - return drawableForColor(Color.parseColor(color)); - } catch (Throwable th) { - return newTransparentDrawable(); - } - } - - protected Drawable drawableForColor (int color) { - return new ColorDrawable (color); - } - - protected Drawable newTransparentDrawable () { - return new ColorDrawable (Color.TRANSPARENT); - } - protected float parseFloat (String value) { try { return Float.parseFloat(value); @@ -716,7 +573,7 @@ public class ViewAttrAdapter implements IAttributeAdapter { return (int) TypedValue.applyDimension(getUnitForDimensionType(dimensionType), dimen, dm); } else if (c == '@') { String name = value.substring("@dimen/".length()); - return parseDimension(resFinder.findDimension(name), defaultValue, dm); + return parseDimension(resourceFinder.findDimension(name), defaultValue, dm); } else if (Character.isLetter(c)) { // This could be one of the following : // 1. match_parent diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewGroupAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewGroupAttrAdapter.java index dc56388bb..a3adfebe7 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewGroupAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/view/ViewGroupAttrAdapter.java @@ -18,12 +18,14 @@ package com.itsaky.inflater.adapters.android.view; import android.animation.LayoutTransition; +import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import org.jetbrains.annotations.Contract; @@ -34,6 +36,10 @@ import org.jetbrains.annotations.Contract; */ public class ViewGroupAttrAdapter extends ViewAttrAdapter { + public ViewGroupAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof ViewGroup; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSeekBarAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSeekBarAttrAdapter.java index 6c297251f..872cc745b 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSeekBarAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSeekBarAttrAdapter.java @@ -17,10 +17,12 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.AbsSeekBar; import androidx.annotation.NonNull; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute adapter for handling attributes related to @@ -29,7 +31,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public abstract class AbsSeekBarAttrAdapter extends ProgressBarAttrAdapter { - + + public AbsSeekBarAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof AbsSeekBar; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSpinnerAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSpinnerAttrAdapter.java index f7ce2358c..15373b144 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSpinnerAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AbsSpinnerAttrAdapter.java @@ -17,13 +17,21 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.AbsSpinner; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; public class AbsSpinnerAttrAdapter extends AdapterViewAttrAdapter { - + + public AbsSpinnerAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof AbsSpinner; @@ -45,7 +53,7 @@ public class AbsSpinnerAttrAdapter extends AdapterViewAttrAdapter { switch (name) { case "entries" : - final var adapter = newSimpleAdapter(context, resFinder.findArray(value)); + final var adapter = newSimpleAdapter(context, resourceFinder.findArray(value)); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); break; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AdapterViewAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AdapterViewAttrAdapter.java index a4ebbb886..a3d6378a6 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AdapterViewAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/AdapterViewAttrAdapter.java @@ -18,11 +18,15 @@ package com.itsaky.inflater.adapters.android.widget; import android.content.Context; +import android.util.DisplayMetrics; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter; /** @@ -32,7 +36,11 @@ import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter; * @author Akash Yadav */ public abstract class AdapterViewAttrAdapter extends ViewGroupAttrAdapter { - + + public AdapterViewAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof AdapterView; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ButtonAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ButtonAttrAdapter.java index d704d7829..cb653f4bc 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ButtonAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ButtonAttrAdapter.java @@ -17,10 +17,14 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.Button; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Adapter for handling attributes related to Button. @@ -31,7 +35,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public class ButtonAttrAdapter extends TextViewAttrAdapter { - + + public ButtonAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof Button; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckBoxAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckBoxAttrAdapter.java index 3ef08395d..216a730a7 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckBoxAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckBoxAttrAdapter.java @@ -16,16 +16,24 @@ */ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.CheckBox; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute handler for handling attributes related to CheckBox */ public class CheckBoxAttrAdapter extends CompondButtonAttrAdapter { - + + public CheckBoxAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof CheckBox; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckedTextViewAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckedTextViewAttrAdapter.java index 91d3d5ae4..816762e9b 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckedTextViewAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CheckedTextViewAttrAdapter.java @@ -16,10 +16,14 @@ */ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.CheckedTextView; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute handler for handling attributes related to @@ -28,7 +32,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public class CheckedTextViewAttrAdapter extends TextViewAttrAdapter { - + + public CheckedTextViewAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof CheckedTextView; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CompondButtonAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CompondButtonAttrAdapter.java index ad89d6122..84629c469 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CompondButtonAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/CompondButtonAttrAdapter.java @@ -1,10 +1,14 @@ package com.itsaky.inflater.adapters.android.widget; import android.content.Context; +import android.util.DisplayMetrics; import android.view.View; import android.widget.CompoundButton; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute handler for handling attibutes to CompoundButton @@ -12,7 +16,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public class CompondButtonAttrAdapter extends ButtonAttrAdapter { - + + public CompondButtonAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof CompoundButton; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/EditTextAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/EditTextAttrAdapter.java index e182b0284..81803be1e 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/EditTextAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/EditTextAttrAdapter.java @@ -17,13 +17,21 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.EditText; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; public class EditTextAttrAdapter extends TextViewAttrAdapter { + public EditTextAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof EditText; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageButtonAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageButtonAttrAdapter.java index 16caa6667..56505e92e 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageButtonAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageButtonAttrAdapter.java @@ -16,10 +16,14 @@ */ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.ImageButton; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute handler for handling attributes related to @@ -29,6 +33,10 @@ import com.itsaky.inflater.IAttribute; */ public class ImageButtonAttrAdapter extends ImageViewAttrAdapter { + public ImageButtonAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof ImageButton; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageViewAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageViewAttrAdapter.java index 404144a16..34af8afce 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageViewAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ImageViewAttrAdapter.java @@ -22,8 +22,11 @@ import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; import android.view.View; import android.widget.ImageView; + +import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter; /** @@ -33,7 +36,11 @@ import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter; * @author Akash Yadav */ public class ImageViewAttrAdapter extends ViewAttrAdapter { - + + public ImageViewAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof ImageView; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/LinearLayoutAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/LinearLayoutAttrAdapter.java index 6d5a6772b..748d452da 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/LinearLayoutAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/LinearLayoutAttrAdapter.java @@ -16,12 +16,14 @@ */ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.LinearLayout; import androidx.annotation.NonNull; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter; /** @@ -30,7 +32,11 @@ import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter; * @author Akash Yadav */ public class LinearLayoutAttrAdapter extends ViewGroupAttrAdapter { - + + public LinearLayoutAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof LinearLayout; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ProgressBarAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ProgressBarAttrAdapter.java index 551af7b93..b46cc3220 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ProgressBarAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/ProgressBarAttrAdapter.java @@ -21,7 +21,11 @@ import android.content.Context; import android.util.DisplayMetrics; import android.view.View; import android.widget.ProgressBar; + +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter; /** @@ -31,7 +35,11 @@ import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter; * @author Akash Yadav */ public class ProgressBarAttrAdapter extends ViewAttrAdapter { - + + public ProgressBarAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof ProgressBar; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RadioButtonAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RadioButtonAttrAdapter.java index 6934f29f3..8fe8532c2 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RadioButtonAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RadioButtonAttrAdapter.java @@ -17,10 +17,14 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.RadioButton; +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute adapter for handling attributes related to @@ -29,7 +33,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public class RadioButtonAttrAdapter extends CompondButtonAttrAdapter { - + + public RadioButtonAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof RadioButton; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RelativeLayoutAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RelativeLayoutAttrAdapter.java index ebb1f3c6a..cc6481392 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RelativeLayoutAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/RelativeLayoutAttrAdapter.java @@ -16,9 +16,14 @@ */ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter; /** @@ -28,7 +33,11 @@ import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter; * @author Akash Yadav */ public class RelativeLayoutAttrAdapter extends ViewGroupAttrAdapter { - + + public RelativeLayoutAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof RelativeLayout; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SeekBarAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SeekBarAttrAdapter.java index b537d706d..7c57f4a20 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SeekBarAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SeekBarAttrAdapter.java @@ -17,12 +17,14 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.SeekBar; import androidx.annotation.NonNull; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute adapter for handling attributes related to @@ -31,7 +33,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public class SeekBarAttrAdapter extends AbsSeekBarAttrAdapter { - + + public SeekBarAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof SeekBar; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SpinnerAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SpinnerAttrAdapter.java index 1ff54a229..f868e9bd5 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SpinnerAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/SpinnerAttrAdapter.java @@ -17,12 +17,14 @@ package com.itsaky.inflater.adapters.android.widget; +import android.util.DisplayMetrics; import android.view.View; import android.widget.Spinner; import androidx.annotation.NonNull; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; /** * Attribute adapter for handling attributes related to @@ -31,7 +33,11 @@ import com.itsaky.inflater.IAttribute; * @author Akash Yadav */ public class SpinnerAttrAdapter extends AbsSpinnerAttrAdapter { - + + public SpinnerAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof Spinner; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/TextViewAttrAdapter.java b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/TextViewAttrAdapter.java index a6aa9ef64..960ccb204 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/TextViewAttrAdapter.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/adapters/android/widget/TextViewAttrAdapter.java @@ -33,6 +33,7 @@ import androidx.annotation.NonNull; import com.blankj.utilcode.util.SizeUtils; import com.itsaky.inflater.IAttribute; +import com.itsaky.inflater.IResourceFinder; import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter; import java.util.regex.Pattern; @@ -42,7 +43,11 @@ import java.util.regex.Pattern; * @author Akash Yadav */ public class TextViewAttrAdapter extends ViewAttrAdapter { - + + public TextViewAttrAdapter (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (resourceFinder, displayMetrics); + } + @Override public boolean isApplicableTo(View view) { return view instanceof TextView; diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/drawable/DrawableParserFactory.java b/xml-inflater/src/main/java/com/itsaky/inflater/drawable/DrawableParserFactory.java new file mode 100644 index 000000000..f6ab721fc --- /dev/null +++ b/xml-inflater/src/main/java/com/itsaky/inflater/drawable/DrawableParserFactory.java @@ -0,0 +1,142 @@ +/* + * 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.inflater.drawable; + +import android.content.Context; +import android.graphics.NinePatch; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.util.DisplayMetrics; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.blankj.utilcode.util.FileIOUtils; +import com.blankj.utilcode.util.ImageUtils; +import com.itsaky.inflater.IResourceFinder; +import com.sdsmdg.harjot.vectormaster.VectorMasterDrawable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +/** + * Utility methods for creating instances of suitable drawable parsers for a file + * or an XML code. + * + * @author Akash Yadav + */ +public abstract class DrawableParserFactory { + + /** + * Create a new drawable parser for the given file. If the given file is not an XML Document, + * then a no-op parser is returned which simply returns the drawable for that file. If the file + * is an XML document, then a suitable parser is determined by looking at the root tag of the + * drawable. + * + * @param context The context. This will be used for obtaining app resources. + * @param file The file to parse. + * @param resourceFinder The resource finder. Used for finding references to another resources. + * @return A new {@link IDrawableParser} instance if the inputs are valid, or {@code null} if there were any + * issues creating the parser. + * @throws XmlPullParserException Thrown by the {@link XmlPullParser#setInput(Reader)}. + * @throws IOException Thrown by {@link XmlPullParser#next()} + */ + @Nullable + public static IDrawableParser newParser (@NonNull final Context context, @NonNull final File file, final IResourceFinder resourceFinder) throws XmlPullParserException, IOException { + if (file.getName ().endsWith (".xml")) { + final var code = FileIOUtils.readFile2String (file); + return DrawableParserFactory.newXmlDrawableParser (code, resourceFinder, context.getResources ().getDisplayMetrics ()); + } else { + final var bitmap = ImageUtils.getBitmap (file); + if (bitmap == null) { + return null; + } + + final var chunk = bitmap.getNinePatchChunk (); + if (NinePatch.isNinePatchChunk (chunk)) { + return new NoParser (new NinePatchDrawable (context.getResources (), new NinePatch (bitmap, chunk))); + } + + return new NoParser (new BitmapDrawable (context.getResources (), bitmap)); + } + } + + /** + * Create a new drawable parser for the given xml code. + * + * @param xmlDrawable The XML code. This must be valid. + * @param resourceFinder The resource finder. Used for finding references to another resources. + * @param displayMetrics This will be used by generated parsers to parse dimension vlaues. + * @return A new {@link IDrawableParser} instance if the inputs are valid, or {@code null} if there were any + * issues creating the parser. + * @throws XmlPullParserException Thrown by the {@link XmlPullParser#setInput(Reader)}. + * @throws IOException Thrown by {@link XmlPullParser#next()} + */ + @Nullable + public static IDrawableParser newXmlDrawableParser (String xmlDrawable, IResourceFinder resourceFinder, DisplayMetrics displayMetrics) throws XmlPullParserException, IOException { + final var factory = XmlPullParserFactory.newInstance (); + factory.setNamespaceAware (true); + + final var parser = factory.newPullParser (); + parser.setInput (new StringReader (xmlDrawable)); + + var event = parser.getEventType (); + while (event != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final var name = parser.getName (); + switch (name) { + case "shape" : + return new ShapeDrawableParser (parser, resourceFinder, displayMetrics); + case "vector" : + return new NoParser (VectorMasterDrawable.fromXML (xmlDrawable)); + default: + return null; + } + } + event = parser.next (); + } + + // We don't support parsing of this type of drawable + return null; + } + + /** + * Instead of parsing anything, this returns the provided drawable. + */ + private static class NoParser extends IDrawableParser { + + private final Drawable parsed; + + protected NoParser (final Drawable parsed) { + super (null, null, null); + this.parsed = parsed; + } + + @Override + public Drawable parse () { + return parsed; + } + } +} diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/drawable/IDrawableParser.java b/xml-inflater/src/main/java/com/itsaky/inflater/drawable/IDrawableParser.java new file mode 100644 index 000000000..2bf68501e --- /dev/null +++ b/xml-inflater/src/main/java/com/itsaky/inflater/drawable/IDrawableParser.java @@ -0,0 +1,74 @@ +/* + * 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.inflater.drawable; + +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.DisplayMetrics; + +import com.itsaky.inflater.IResourceFinder; +import com.itsaky.inflater.util.CommonParseUtils; + +import org.xmlpull.v1.XmlPullParser; + +/** + * Base class for drawable parsers. + * + * @author Akash Yadav + */ +public abstract class IDrawableParser extends CommonParseUtils { + + protected final XmlPullParser parser; + + protected IDrawableParser (XmlPullParser parser, IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super(resourceFinder, displayMetrics); + this.parser = parser; + } + + public abstract Drawable parse () throws Exception; + + protected int attrIndex (final String name) { + for (int i = 0; i < this.parser.getAttributeCount (); i++) { + if (this.parser.getAttributeName (i).equals (name)) { + return i; + } + } + + return -1; + } + + protected String value (int index) { + return parser.getAttributeValue (index); + } + + protected boolean isApi26() { + return Build.VERSION.SDK_INT >= 26; + } + + protected boolean isApi28() { + return Build.VERSION.SDK_INT >= 28; + } + + protected boolean isApi29() { + return Build.VERSION.SDK_INT >= 29; + } + + protected boolean isApi30() { + return Build.VERSION.SDK_INT >= 30; + } +} diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/drawable/ShapeDrawableParser.java b/xml-inflater/src/main/java/com/itsaky/inflater/drawable/ShapeDrawableParser.java new file mode 100644 index 000000000..1b0fd6d4d --- /dev/null +++ b/xml-inflater/src/main/java/com/itsaky/inflater/drawable/ShapeDrawableParser.java @@ -0,0 +1,349 @@ +/* + * 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.inflater.drawable; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.util.DisplayMetrics; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.itsaky.androidide.app.BaseApplication; +import com.itsaky.inflater.IResourceFinder; + +import org.jetbrains.annotations.Contract; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Arrays; + +/** + * Parses a drawable whose root element is + * {@code }. If the parse was successful, returns a {@link GradientDrawable}, + * otherwise {@code null}. + * + * @author Akash Yadav + */ +public class ShapeDrawableParser extends IDrawableParser { + + protected ShapeDrawableParser (XmlPullParser parser, IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + super (parser, resourceFinder, displayMetrics); + } + + @Nullable + @Override + public Drawable parse () throws Exception { + + final var drawable = new GradientDrawable (); + + var index = attrIndex ("shape"); + drawable.setShape (index == -1 ? GradientDrawable.RECTANGLE : parseShape (this.parser.getAttributeValue (index))); + + if (isApi29 ()) { + index = attrIndex ("innerRadius"); + drawable.setInnerRadius (index == -1 ? 0 : parseDimension (value (index), 0)); + + index = attrIndex ("innerRadiusRatio"); + drawable.setInnerRadiusRatio (index == -1 ? 1f : Float.parseFloat (value (index))); + + index = attrIndex ("thickness"); + drawable.setThickness (index == -1 ? 0 : parseDimension (value (index), 0)); + + index = attrIndex ("thicknessRatio"); + drawable.setThicknessRatio (index == -1 ? 1f : Float.parseFloat (value (index))); + } + + index = attrIndex ("useLevel"); + drawable.setUseLevel (index != -1 && parseBoolean (value (index))); + + var event = parser.getEventType (); + while (event != XmlPullParser.END_DOCUMENT) { + final var name = parser.getName (); + if (event == XmlPullParser.START_TAG) { + switch (name) { + case "corners": + parseCorners (drawable); + break; + case "gradient": + parseGradient (drawable); + break; + case "padding" : + parsePadding (drawable); + break; + case "size" : + parseSize (drawable); + break; + case "solid" : + parseSolid (drawable); + break; + case "stroke" : + parseStroke (drawable); + break; + } + } + event = parser.next (); + } + + return drawable; + } + + private void parseStroke (@NonNull final GradientDrawable drawable) { + var index = attrIndex ("width"); + var strokeWidth = 0; + if (index != - 1) { + drawable.setStroke (strokeWidth = parseDimension (value (index), 0), Color.TRANSPARENT); + } + + index = attrIndex ("color"); + var strokeColor = Color.TRANSPARENT; + if (index != -1) { + drawable.setStroke (strokeWidth, strokeColor = parseColor (value (index), appContext ())); + } + + index = attrIndex ("dashWidth"); + var dashWidth = 0; + if (index != -1) { + drawable.setStroke (strokeWidth, strokeColor, dashWidth = parseDimension (value (index), 0), 0); + } + + index = attrIndex ("dashGap"); + if (index != -1) { + drawable.setStroke (strokeWidth, strokeColor, dashWidth, parseDimension (value (index), 0)); + } + } + + private void parseSolid (@NonNull final GradientDrawable drawable) { + var index = attrIndex ("color"); + if (index != -1) { + drawable.setColor (parseColor (value (index), appContext ())); + } + } + + private void parseSize (@NonNull final GradientDrawable drawable) { + var index = attrIndex ("width"); + if (index != -1) { + drawable.setSize (parseDimension (value (index), 0), drawable.getIntrinsicHeight ()); + } + + index = attrIndex ("height"); + if (index != -1) { + drawable.setSize (drawable.getIntrinsicWidth (), parseDimension (value (index), 0)); + } + } + + private void parsePadding (@NonNull final GradientDrawable drawable) { + + // Padding is available from API 29 only + if (!isApi29 ()) { + return; + } + + final var rect = new Rect (); + if (!drawable.getPadding (rect)) { + rect.set (0,0,0,0); + } + + var changed = false; + var index = attrIndex ("left"); + if (index != -1) { + rect.left = parseDimension (value (index), 0); + changed = true; + } + + index = attrIndex ("top"); + if (index != -1) { + rect.top = parseDimension (value (index), 0); + changed = true; + } + + index = attrIndex ("right"); + if (index != -1) { + rect.right = parseDimension (value (index), 0); + changed = true; + } + + index = attrIndex ("bottom"); + if (index != -1) { + rect.bottom = parseDimension (value (index), 0); + changed = true; + } + + if (changed) { + drawable.setPadding (rect.left, rect.top, rect.right, rect.bottom); + } + } + + private void parseGradient (@NonNull final GradientDrawable drawable) { + var index = attrIndex ("angle"); + drawable.setOrientation (index == -1 ? GradientDrawable.Orientation.LEFT_RIGHT : parseGradientOrientation (value (index))); + + index = attrIndex ("centerX"); + var center = index == - 1 ? drawable.getGradientCenterX () : Float.parseFloat (value (index)); + drawable.setGradientCenter (center, drawable.getGradientCenterY ()); + + index = attrIndex ("centerY"); + center = index == - 1 ? drawable.getGradientCenterX () : Float.parseFloat (value (index)); + drawable.setGradientCenter (drawable.getGradientCenterX (), center); + + final var colors = new int [3]; + var changed = false; + Arrays.fill (colors, -1); + + index = attrIndex ("centerColor"); + if (index != -1) { + colors [1] = parseColor (value (index), appContext ()); + changed = true; + } + + index = attrIndex ("endColor"); + if (index != -1) { + colors [2] = parseColor (value (index), appContext ()); + changed = true; + } + + index = attrIndex ("gradientRadius"); + drawable.setGradientRadius (index == -1 ? drawable.getGradientRadius () : Float.parseFloat (value (index))); + + index = attrIndex ("startColor"); + if (index != -1) { + colors[0] = parseColor (value (index), appContext ()); + changed = true; + } + + if (changed) { + drawable.setColors (colors); + } + + index = attrIndex ("type"); + drawable.setGradientType (index == -1 ? GradientDrawable.LINEAR_GRADIENT : parseGradientType (value (index))); + + index = attrIndex ("useLevel"); + drawable.setUseLevel (index != -1 && parseBoolean (value (index))); + } + + private int parseGradientType (String value) { + switch (value) { + case "radial" : + return GradientDrawable.RADIAL_GRADIENT; + case "sweep" : + return GradientDrawable.SWEEP_GRADIENT; + case "linear" : + default: + return GradientDrawable.LINEAR_GRADIENT; + } + } + + private GradientDrawable.Orientation parseGradientOrientation (String value) { + final var angle = Integer.parseInt (value); + + // Angle must be between 0-315 and a multiple of 45 + // Angle moves in anti-clockwise direction + if (angle == 45) { + return GradientDrawable.Orientation.BL_TR; + } + + if (angle == 90) { + return GradientDrawable.Orientation.BOTTOM_TOP; + } + + if (angle == 135) { + return GradientDrawable.Orientation.BR_TL; + } + + if (angle == 180) { + return GradientDrawable.Orientation.RIGHT_LEFT; + } + + if (angle == 225) { + return GradientDrawable.Orientation.TR_BL; + } + + if (angle == 270) { + return GradientDrawable.Orientation.TOP_BOTTOM; + } + + if (angle == 315) { + return GradientDrawable.Orientation.TL_BR; + } + + // Angle 0 or any invalid valid should make the orientation left to right + return GradientDrawable.Orientation.LEFT_RIGHT; + } + + private void parseCorners (@NonNull GradientDrawable drawable) { + int index; + index = attrIndex ("radius"); + drawable.setCornerRadius (index == -1 ? 0f : parseDimension (value (index), 0)); + + var changed = false; + final var radii = new float[8]; + Arrays.fill (radii, 0f); + + index = attrIndex ("topLeftRadius"); + if (index != -1) { + radii[0] = radii[1] = parseDimension (value (index), 0); + changed = true; + } + + index = attrIndex ("topRightRadius"); + if (index != -1) { + radii[2] = radii[3] = parseDimension (value (index), 0); + changed = true; + + } + + index = attrIndex ("bottomLeftRadius"); + if (index != -1) { + radii[4] = radii[5] = parseDimension (value (index), 0); + changed = true; + + } + + index = attrIndex ("bottomRightRadius"); + if (index != -1) { + radii[6] = radii[7] = parseDimension (value (index), 0); + changed = true; + } + + if (changed) { + drawable.setCornerRadii (radii); + } + } + + @Contract(pure = true) + private int parseShape (@NonNull String value) { + switch (value) { + case "oval" : + return GradientDrawable.OVAL; + case "line": + return GradientDrawable.LINE; + case "ring" : + return GradientDrawable.RING; + case "rectangle" : + default: + return GradientDrawable.RECTANGLE; + } + } + + protected Context appContext () { + return BaseApplication.getBaseInstance (); + } +} diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/util/CommonParseUtils.java b/xml-inflater/src/main/java/com/itsaky/inflater/util/CommonParseUtils.java new file mode 100644 index 000000000..354f69495 --- /dev/null +++ b/xml-inflater/src/main/java/com/itsaky/inflater/util/CommonParseUtils.java @@ -0,0 +1,235 @@ +/* + * 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.inflater.util; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.NinePatch; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.blankj.utilcode.util.ImageUtils; +import com.itsaky.androidide.utils.Logger; +import com.itsaky.inflater.IResourceFinder; +import com.itsaky.inflater.drawable.DrawableParserFactory; +import com.sdsmdg.harjot.vectormaster.VectorMasterDrawable; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.regex.Pattern; + +/** + * Contains methods which are commonly used by layout inflater, + * attribute adapters and drawable parsers. + * + * @author Akash Yadav + */ +public class CommonParseUtils { + + private static final Pattern HEX_COLOR = Pattern.compile("#[a-fA-F0-9]{6,8}"); + + protected IResourceFinder resourceFinder; + protected final DisplayMetrics displayMetrics; + + private static final Logger LOG = Logger.instance ("CommonParseUtils"); + + public CommonParseUtils (@NonNull IResourceFinder resourceFinder, DisplayMetrics displayMetrics) { + this.resourceFinder = resourceFinder; + this.displayMetrics = displayMetrics; + } + + protected boolean parseBoolean(@NonNull String value) { + if (!value.startsWith("@")) { + if ("true".equals(value)) { + return true; + } else if ("false".equals(value)) { + return false; + } + } else { + // TODO Find resource value in booleans.xml + } + return false; + } + + protected int parseDimension(final String value, int defaultValue) { + if (value == null) { + return defaultValue; + } + + char c = value.charAt(0); + if (Character.isDigit(c)) { + // A dimension value which starts with a digit. E.g.: 1dp, 12sp, 123px, etc. + String dimensionVal = ""; + int index = 0; + while (Character.isDigit(c = value.charAt(index))) { + dimensionVal += c; + index ++; + } + + final int dimen = Integer.parseInt(dimensionVal); + final String dimensionType = value.substring(index); + return (int) TypedValue.applyDimension(getUnitForDimensionType(dimensionType), dimen, displayMetrics); + } else if (c == '@') { + String name = value.substring("@dimen/".length()); + return parseDimension(resourceFinder.findDimension(name), defaultValue); + } else if (Character.isLetter(c)) { + // This could be one of the following : + // 1. match_parent + // 2. wrap_content + // 3. fill_parent + switch (value) { + case "match_parent" : + case "fill_parent" : + return ViewGroup.LayoutParams.MATCH_PARENT; + case "wrap_content" : + default : + return ViewGroup.LayoutParams.WRAP_CONTENT; + } + } + + return defaultValue; + } + + protected int getUnitForDimensionType(@NonNull String dimensionType) { + switch (dimensionType) { + case "dp" : + return TypedValue.COMPLEX_UNIT_DIP; + case "sp" : + return TypedValue.COMPLEX_UNIT_SP; + case "px" : + return TypedValue.COMPLEX_UNIT_PX; + case "pt" : + return TypedValue.COMPLEX_UNIT_PT; + case "in" : + return TypedValue.COMPLEX_UNIT_IN; + case "mm" : + return TypedValue.COMPLEX_UNIT_MM; + } + return TypedValue.COMPLEX_UNIT_DIP; + } + + protected int parseColor (String color, final Context ctx) { + if (HEX_COLOR.matcher(color).matches()) { + try { + return Color.parseColor(color); + } catch (Throwable th) { + // Ignored + } + } else if (color.startsWith("@color/")) { + return parseColor(resourceFinder.findColor(color.substring("@color/".length())), ctx); + } else if (color.startsWith("@android:color/")) { + final int id = findAndroidResId("color", color.substring("@android:color/".length())); + return ContextCompat.getColor(ctx, id); + } + + return Color.parseColor("#00ffffff"); + } + + protected Drawable parseDrawable (String value, final Context ctx) { + if (HEX_COLOR.matcher(value).matches()) { + return drawableForColor(value); + } else if (value.startsWith("@")) { + + // First check if this is a reference to an android resource + if (value.startsWith("@android:")) { + final String typeAndValue = value.substring("@android:".length()); + final String[] split = typeAndValue.split(Pattern.quote("/")); // For @android:color/white, it will be ["color", "white"] + final String type = split[0]; + final String typeVal = split[1]; + final int id = findAndroidResId (type, typeVal); + + if (id != -1) { + switch (type) { + case "color" : + return drawableForColor(ContextCompat.getColor(ctx, id)); + case "drawable" : + return ContextCompat.getDrawable(ctx, id); + default : + return newTransparentDrawable(); + } + } + + } else { + // We found a reference to another resource + if (value.startsWith("@drawable/")) { + final File drawable = resourceFinder.findDrawable (value.substring("@drawable/".length())); + + if (drawable == null) { + return null; + } + + try { + final var parser = DrawableParserFactory.newParser (ctx, drawable, resourceFinder); + if (parser == null) { + return null; + } + + return parser.parse(); + } catch (Exception e) { + LOG.error ("Error parsing drawable", e); + return null; + } + } else if (value.startsWith("@color/")) { + final String color = resourceFinder.findColor(value.substring("@color/".length())); + // TODO Check if this color resource is a selector + return parseDrawable(color, ctx); + } + } + } + return newTransparentDrawable(); + } + + private int findAndroidResId(String type, String name) { + try { + final Class typeClass = android.R.class.getClassLoader().loadClass("android.R$" + type); + final Field typeField = typeClass.getDeclaredField(name); + typeField.setAccessible(true); + return (int) typeField.get(null); + } catch (Throwable th) { + return -1; + } + } + + protected Drawable drawableForColor (String color) { + try { + return drawableForColor(Color.parseColor(color)); + } catch (Throwable th) { + return newTransparentDrawable(); + } + } + + protected Drawable drawableForColor (int color) { + return new ColorDrawable (color); + } + + protected Drawable newTransparentDrawable () { + return new ColorDrawable (Color.TRANSPARENT); + } +}