Fix: Invalid XML code generated by UI Designer

This commit is contained in:
Akash Yadav
2022-01-08 12:31:57 +05:30
parent cdcb23c9a9
commit 027f6aa554
5 changed files with 147 additions and 34 deletions
@@ -21,6 +21,10 @@ package com.itsaky.widgets;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.widgets.models.Widget;
import java.io.BufferedReader;
import java.io.IOException;
@@ -28,26 +32,39 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
public class WidgetInfo {
private final SortedMap<String, Widget> widgets;
public WidgetInfo (final Context ctx) throws IOException {
public WidgetInfo (@NonNull final Context ctx) throws IOException {
this.widgets = new TreeMap<>();
readWidgets(ctx.getResources());
}
public Widget getWidgetBySimpleName (String simpleName) {
return this.widgets.getOrDefault(simpleName, null);
public Widget getWidget (String name) {
return this.widgets.getOrDefault(name, null);
}
@Nullable
public Widget getWidgetBySimpleName (@NonNull final String name) {
for (final var entry : this.widgets.entrySet ()) {
final var key = entry.getKey ();
final var simple = key.substring (key.lastIndexOf (".") + 1);
if (simple.equals (name)) {
return entry.getValue ();
}
}
return null;
}
public Collection<Widget> getWidgets() {
return this.widgets.values();
}
private void readWidgets(final Resources resources) throws IOException {
private void readWidgets(@NonNull final Resources resources) throws IOException {
final InputStream in = resources.openRawResource(com.itsaky.sdkinfo.R.raw.widgets);
if(in == null) return;
@@ -60,17 +77,12 @@ public class WidgetInfo {
final char code = name.charAt(0);
final String viewName = name.substring(1);
final String simpleViewName = simpleName(viewName);
final boolean isViewGroup = code == 'L'; // L -> Layout, W -> Widget, P -> LayoutParam
// Don't add layout params
if(code != 'P') {
widgets.put(simpleViewName, new Widget(viewName, simpleViewName, isViewGroup));
widgets.put(viewName, new Widget(viewName, isViewGroup));
}
}
}
private String simpleName(String name) {
return name.substring(name.lastIndexOf(".") + 1);
}
}
@@ -19,24 +19,26 @@
**************************************************************************************/
package com.itsaky.widgets.models;
public class Widget implements Comparable {
import androidx.annotation.NonNull;
public class Widget implements Comparable <Widget> {
public String name;
public String simpleName;
public boolean isViewGroup;
public Widget(String name, String simpleName, boolean isViewGroup) {
public Widget(@NonNull String name, boolean isViewGroup) {
this.name = name;
this.simpleName = simpleName;
this.simpleName = name.substring (name.lastIndexOf (".") + 1);
this.isViewGroup = isViewGroup;
}
@Override
public int compareTo(Object p1) {
if(p1 instanceof Widget) {
Widget that = (Widget) p1;
public int compareTo(Widget that) {
if(that != null) {
return this.simpleName.compareTo(that.simpleName);
}
return -1;
}
@@ -34,6 +34,7 @@ import com.itsaky.attrinfo.AttrInfo;
import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter;
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
import com.itsaky.inflater.impl.BaseView;
import com.itsaky.inflater.impl.ErrorUiView;
import com.itsaky.inflater.impl.UiAttribute;
import com.itsaky.inflater.impl.UiView;
import com.itsaky.inflater.impl.UiViewGroup;
@@ -43,6 +44,7 @@ import com.itsaky.widgets.models.Widget;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@@ -104,7 +106,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
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", parent);
}
// Notify
@@ -267,7 +269,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
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));
return onCreateErrorView (name, getString (com.itsaky.inflater.R.string.msg_cannot_create_view, name), parent);
}
return createFromQualifiedName (widget.name, parent, style);
@@ -278,6 +280,13 @@ class XMLLayoutInflater extends BaseLayoutInflater {
// TODO Try to load classes directly from .class files if possible
try {
final var androidView = this.widgetInfo.getWidget (name);
if (androidView == null) {
// If this is not an Android view, do not bother to create one
throw new InflateException (String.format ("%s is not supported yet.", name));
}
final View created = createAndroidViewForName (name);
final BaseView view =
created instanceof ViewGroup
@@ -287,7 +296,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
} catch (Throwable th) {
final var msg = getString (R.string.msg_cannot_create_view, name);
LOG.error (msg, th);
return onCreateErrorView (name, msg);
return onCreateErrorView (name, msg, parent);
}
}
@@ -295,8 +304,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
protected View createAndroidViewForName (String name) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, java.lang.reflect.InvocationTargetException {
final Class<? extends View> loaded = Class.forName (name).asSubclass (View.class);
final Constructor<? extends View> constructor = loaded.getConstructor (Context.class /*, AttributeSet.class, int.class*/);
final View created = constructor.newInstance (contextProvider.getContext ()/*, null, style*/);
return created;
return constructor.newInstance (contextProvider.getContext ()/*, null, style*/);
}
protected IView applyLayoutParams (@NonNull IView view, ViewGroup parent) {
@@ -305,18 +313,9 @@ class XMLLayoutInflater extends BaseLayoutInflater {
return view;
}
protected IView onCreateErrorView (String name, String msg) throws InflateException {
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));
return new UiView (name, error, true);
protected IView onCreateErrorView (String name, String msg, final ViewGroup parent) throws InflateException {
final var view = ErrorUiView.create (Objects.requireNonNull (contextProvider.getContext (), "Context is null."), name, msg);
return applyLayoutParams (view, parent);
}
private String getString (@StringRes int id, Object... format) throws InflateException {
@@ -329,6 +328,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
try {
final Class<?> clazz = parent.getClass ();
Method method = null;
try {
method = clazz.getDeclaredMethod ("generateDefaultLayoutParams");
} catch (NoSuchMethodException | SecurityException e) {
@@ -72,7 +72,6 @@ public class ViewAttrAdapter extends CommonParseUtils implements IAttributeAdapt
final String name = attribute.getAttributeName();
final String value = attribute.getValue();
final ViewGroup.LayoutParams params = view.getLayoutParams();
final Resources res = view.getResources();
final Context ctx = view.getContext();
if (!canHandleNamespace(namespace)) {
@@ -0,0 +1,100 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.inflater.impl;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.itsaky.inflater.IView;
import org.jetbrains.annotations.Contract;
/**
* A view which is used to show views that cannot be inflated.
* This view will preserver the attributes and the children
* if the view is a view group;
*/
public class ErrorUiView extends UiViewGroup {
private ErrorUiView (String qualifiedName, ViewGroup view) {
super (qualifiedName, view);
}
private ErrorUiView (String qualifiedName, ViewGroup view, boolean isPlaceholder) {
super (qualifiedName, view, isPlaceholder);
}
/**
* Create a new error view for the given qualified name of the view
* that was not inflated correctly.
*
* We add a text directly in this view. Any other views that will
* be added to this group will have visibility {@link View#GONE}
* by default.
*
* @param context The context that will be used to create the view.
* @param name The qualified name of the view that was not inflated.
* @param message The message that should be shown to the user.
* @return A newly created instance of the error view.
*/
@NonNull
@Contract("_, _, _ -> new")
public static IView create (@NonNull final Context context, @NonNull final String name, @NonNull final String message) {
final var container = new LinearLayout (context);
final var error = new TextView (context);
error.setText (message);
error.setLayoutParams (new ViewGroup.LayoutParams (-2, -2));
error.setTextColor (ContextCompat.getColor (context, android.R.color.black));
error.setBackgroundColor (ContextCompat.getColor (context, android.R.color.white));
container.setOrientation (LinearLayout.HORIZONTAL);
container.addView (error, new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new ErrorUiView (name, container, true);
}
@Override
public void addView (IView view, int index) {
// Adding the view at the given index will result in an error
// This is because the children list is different in this error
// view. Because of TextView added in ErrorView.create (...);
// So we simply add the view at index 0 and make it invisible
super.addView (view, 0);
view.asView ().setVisibility (View.GONE);
}
@Override
public String getXmlTag () {
// This might be a custom view or something
// So return the qualified name of the view
return this.qualifiedName;
}
@NonNull
@Override
public String generateCode (int indentCount) {
return super.generateCode (indentCount);
}
}