com.flipkart.android.proteus.processor.DrawableResourceProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.flipkart.android.proteus.processor.DrawableResourceProcessor.java

Source

/*
 * Copyright 2016 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flipkart.android.proteus.processor;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.LevelListDrawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;

import com.flipkart.android.proteus.parser.ParseHelper;
import com.flipkart.android.proteus.toolbox.ColorUtils;
import com.flipkart.android.proteus.toolbox.NetworkDrawableHelper;
import com.flipkart.android.proteus.toolbox.ProteusConstants;
import com.flipkart.android.proteus.view.ProteusView;
import com.flipkart.android.proteus.view.manager.ProteusViewManager;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;

import java.util.ArrayList;
import java.util.List;

/**
 * Use this as the base processor for references like @drawable or remote resources with http:// urls.
 */
public abstract class DrawableResourceProcessor<V extends View> extends AttributeProcessor<V> {

    private static final String TAG = "DrawableResource";

    private static final String DRAWABLE_RIPPLE = "ripple";
    private static final String DRAWABLE_SELECTOR = "selector";
    private static final String DRAWABLE_SHAPE = "shape";
    private static final String DRAWABLE_LAYER_LIST = "layer-list";
    private static final String DRAWABLE_LEVEL_LIST = "level-list";
    private static final String TYPE = "type";
    private static final String CHILDREN = "children";
    private static final String TYPE_CORNERS = "corners";
    private static final String TYPE_GRADIENT = "gradient";
    private static final String TYPE_PADDING = "padding";
    private static final String TYPE_SIZE = "size";
    private static final String TYPE_SOLID = "solid";
    private static final String TYPE_STROKE = "stroke";
    private static final String SHAPE_RECTANGLE = "rectangle";
    private static final String SHAPE_OVAL = "oval";
    private static final String SHAPE_LINE = "line";
    private static final String SHAPE_RING = "ring";
    private static final String LINEAR_GRADIENT = "linear";
    private static final String RADIAL_GRADIENT = "radial";
    private static final String SWEEP_GRADIENT = "sweep";
    private static Gson sGson = new Gson();

    public static GradientDrawable loadGradientDrawable(Context context, JsonObject value) {
        ShapeDrawableJson shapeDrawable = sGson.fromJson(value, ShapeDrawableJson.class);
        return shapeDrawable.init(context);
    }

    private static Drawable loadRippleDrawable(@NonNull Context context, @NonNull JsonObject value,
            @NonNull String attributeKey, @NonNull View view) {
        RippleDrawableJson rippleDrawable = sGson.fromJson(value, RippleDrawableJson.class);
        return rippleDrawable.init(context, attributeKey, view);
    }

    @Override
    public void handle(String key, JsonElement value, V view) {
        if (value.isJsonPrimitive()) {
            handleString(key, value.getAsString(), view);
        } else if (value.isJsonObject()) {
            handleElement(key, value, view);
        } else {
            if (ProteusConstants.isLoggingEnabled()) {
                Log.e(TAG, "Resource for key: " + key + " must be a primitive or an object. value -> "
                        + value.toString());
            }
        }
    }

    /**
     * This block handles different drawables.
     * Selector and LayerListDrawable are handled here.
     * Override this to handle more types of drawables
     *
     * @param attributeKey
     * @param attributeValue
     * @param view
     */
    protected void handleElement(String attributeKey, JsonElement attributeValue, V view) {

        JsonObject jsonObject = attributeValue.getAsJsonObject();

        JsonElement type = jsonObject.get(TYPE);
        String drawableType = type.getAsString();
        JsonElement childrenElement = null;
        switch (drawableType) {
        case DRAWABLE_SELECTOR:
            final StateListDrawable stateListDrawable = new StateListDrawable();
            childrenElement = jsonObject.get(CHILDREN);
            if (childrenElement != null) {
                JsonArray children = childrenElement.getAsJsonArray();
                for (JsonElement childElement : children) {
                    JsonObject child = childElement.getAsJsonObject();
                    final Pair<int[], JsonElement> state = ParseHelper.parseState(child);
                    if (state != null) {
                        DrawableResourceProcessor<V> processor = new DrawableResourceProcessor<V>() {
                            @Override
                            public void setDrawable(V view, Drawable drawable) {
                                stateListDrawable.addState(state.first, drawable);
                            }
                        };
                        processor.handle(attributeKey, state.second, view);
                    }

                }
            }
            setDrawable(view, stateListDrawable);
            break;
        case DRAWABLE_SHAPE:
            GradientDrawable gradientDrawable = loadGradientDrawable(view.getContext(), jsonObject);
            if (null != gradientDrawable) {
                setDrawable(view, gradientDrawable);
            }
            break;
        case DRAWABLE_LAYER_LIST:
            final List<Pair<Integer, Drawable>> drawables = new ArrayList<>();
            childrenElement = jsonObject.get(CHILDREN);
            if (childrenElement != null) {
                JsonArray children = childrenElement.getAsJsonArray();
                for (JsonElement childElement : children) {
                    JsonObject child = childElement.getAsJsonObject();
                    final Pair<Integer, JsonElement> layerPair = ParseHelper.parseLayer(child);
                    if (null != layerPair) {
                        DrawableResourceProcessor<V> processor = new DrawableResourceProcessor<V>() {
                            @Override
                            public void setDrawable(V view, Drawable drawable) {
                                drawables.add(new Pair<>(layerPair.first, drawable));
                                onLayerDrawableFinish(view, drawables);
                            }
                        };
                        processor.handle(attributeKey, layerPair.second, view);
                    }
                }
            }
            break;
        case DRAWABLE_LEVEL_LIST:
            final LevelListDrawable levelListDrawable = new LevelListDrawable();
            childrenElement = jsonObject.get(CHILDREN);
            if (childrenElement != null) {
                JsonArray children = childrenElement.getAsJsonArray();
                for (JsonElement childElement : children) {
                    LayerListDrawableItem layerListDrawableItem = sGson.fromJson(childElement,
                            LayerListDrawableItem.class);
                    layerListDrawableItem.addItem(view.getContext(), levelListDrawable);
                }
            }
            break;
        case DRAWABLE_RIPPLE:
            Drawable rippleDrawable = loadRippleDrawable(view.getContext(), jsonObject, attributeKey, view);
            if (null != rippleDrawable) {
                setDrawable(view, rippleDrawable);
            }
            break;
        }
    }

    private void onLayerDrawableFinish(V view, List<Pair<Integer, Drawable>> drawables) {
        Drawable[] drawableContainer = new Drawable[drawables.size()];
        // iterate and create an array of drawables to be used for the constructor
        for (int i = 0; i < drawables.size(); i++) {
            Pair<Integer, Drawable> drawable = drawables.get(i);
            drawableContainer[i] = drawable.second;
        }

        // put them in the constructor
        LayerDrawable layerDrawable = new LayerDrawable(drawableContainer);

        // we could have avoided the following loop if layer drawable has a method to add drawable and set id at same time
        for (int i = 0; i < drawables.size(); i++) {
            Pair<Integer, Drawable> drawable = drawables.get(i);
            layerDrawable.setId(i, drawable.first);
            drawableContainer[i] = drawable.second;
        }

        setDrawable(view, layerDrawable);
    }

    /**
     * Any string based drawables are handled here. Color, local resource and remote image urls.
     *
     * @param attributeKey
     * @param attributeValue
     * @param view
     */
    protected void handleString(String attributeKey, final String attributeValue, final V view) {
        ProteusViewManager viewManager = ((ProteusView) view).getViewManager();
        boolean synchronousRendering = viewManager.getLayoutBuilder().isSynchronousRendering();

        if (ParseHelper.isLocalResourceAttribute(attributeValue)) {
            int attributeId = ParseHelper.getAttributeId(view.getContext(), attributeValue);
            if (0 != attributeId) {
                TypedArray ta = view.getContext().obtainStyledAttributes(new int[] { attributeId });
                Drawable drawable = ta.getDrawable(0 /* index */);
                ta.recycle();
                setDrawable(view, drawable);
            }
        } else if (ParseHelper.isColor(attributeValue)) {
            setDrawable(view, new ColorDrawable(ParseHelper.parseColor(attributeValue)));
        } else if (ParseHelper.isLocalDrawableResource(attributeValue)) {
            try {
                Resources r = view.getContext().getResources();
                int drawableId = r.getIdentifier(attributeValue, "drawable", view.getContext().getPackageName());
                Drawable drawable = r.getDrawable(drawableId);
                setDrawable(view, drawable);
            } catch (Exception ex) {
                System.out.println("Could not load local resource " + attributeValue);
            }
        } else if (URLUtil.isValidUrl(attributeValue)) {
            NetworkDrawableHelper.DrawableCallback callback = new NetworkDrawableHelper.DrawableCallback() {
                @Override
                public void onDrawableLoad(String url, final Drawable drawable) {
                    setDrawable(view, drawable);
                }

                @Override
                public void onDrawableError(String url, String reason, Drawable errorDrawable) {
                    System.out.println("Could not load " + url + " : " + reason);
                    if (errorDrawable != null) {
                        setDrawable(view, errorDrawable);
                    }
                }
            };
            new NetworkDrawableHelper(view, attributeValue, synchronousRendering, callback,
                    viewManager.getLayoutBuilder().getNetworkDrawableHelper(), viewManager.getLayout());
        }

    }

    public abstract void setDrawable(V view, Drawable drawable);

    private abstract static class GradientDrawableElement {
        private int mTempColor = 0;

        public abstract void apply(Context context, GradientDrawable gradientDrawable);

        protected int loadColor(Context context, JsonElement colorValue) {
            mTempColor = 0;
            if (null != colorValue && !colorValue.isJsonNull()) {
                ColorUtils.loadColor(context, colorValue, new ValueCallback<Integer>() {
                    @Override
                    public void onReceiveValue(Integer value) {
                        mTempColor = value;
                    }
                }, new ValueCallback<ColorStateList>() {
                    @Override
                    public void onReceiveValue(ColorStateList value) {

                    }
                });
            }
            return mTempColor;
        }
    }

    private static class Corners extends GradientDrawableElement {

        @SerializedName("radius")
        public String radius;
        @SerializedName("topLeftRadius")
        public String topLeftRadius;
        @SerializedName("topRightRadius")
        public String topRightRadius;
        @SerializedName("bottomLeftRadius")
        public String bottomLeftRadius;
        @SerializedName("bottomRightRadius")
        public String bottomRightRadius;

        @Override
        public void apply(Context context, GradientDrawable gradientDrawable) {
            if (!TextUtils.isEmpty(radius)) {
                gradientDrawable.setCornerRadius(ParseHelper.parseDimension(radius, context));
            }

            float fTopLeftRadius = TextUtils.isEmpty(topLeftRadius) ? 0
                    : ParseHelper.parseDimension(topLeftRadius, context);
            float fTopRightRadius = TextUtils.isEmpty(topRightRadius) ? 0
                    : ParseHelper.parseDimension(topRightRadius, context);
            float fBottomRightRadius = TextUtils.isEmpty(bottomRightRadius) ? 0
                    : ParseHelper.parseDimension(bottomRightRadius, context);
            float fBottomLeftRadius = TextUtils.isEmpty(bottomLeftRadius) ? 0
                    : ParseHelper.parseDimension(bottomLeftRadius, context);

            if (fTopLeftRadius != 0 || fTopRightRadius != 0 || fBottomRightRadius != 0 || fBottomLeftRadius != 0) {
                // The corner radii are specified in clockwise order (see Path.addRoundRect())
                gradientDrawable.setCornerRadii(
                        new float[] { fTopLeftRadius, fTopLeftRadius, fTopRightRadius, fTopRightRadius,
                                fBottomRightRadius, fBottomRightRadius, fBottomLeftRadius, fBottomLeftRadius });
            }
        }
    }

    private static class Solid extends GradientDrawableElement {

        @SerializedName("color")
        public JsonElement color;

        @Override
        public void apply(Context context, final GradientDrawable gradientDrawable) {
            ColorUtils.loadColor(context, color, new ValueCallback<Integer>() {
                /**
                 * Invoked when the value is available.
                 *
                 * @param value The value.
                 */
                @Override
                public void onReceiveValue(Integer value) {
                    gradientDrawable.setColor(value);
                }
            }, new ValueCallback<ColorStateList>() {
                /**
                 * Invoked when the value is available.
                 *
                 * @param value The value.
                 */
                @Override
                public void onReceiveValue(ColorStateList value) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        gradientDrawable.setColor(value);
                    }
                }
            });
        }
    }

    private static class Gradient extends GradientDrawableElement {

        @SerializedName("angle")
        public Integer angle;
        @SerializedName("centerX")
        public Float centerX;
        @SerializedName("centerY")
        public Float centerY;
        @SerializedName("centerColor")
        public JsonElement centerColor;
        @SerializedName("endColor")
        public JsonElement endColor;
        @SerializedName("gradientRadius")
        public Float gradientRadius;
        @SerializedName("startColor")
        public JsonElement startColor;
        @SerializedName("gradientType")
        public String gradientType;
        @SerializedName("useLevel")
        public Boolean useLevel;

        public static GradientDrawable.Orientation getOrientation(Integer angle) {
            GradientDrawable.Orientation orientation = GradientDrawable.Orientation.LEFT_RIGHT;

            if (null != angle) {
                angle %= 360;
                if (angle % 45 == 0) {
                    switch (angle) {
                    case 0:
                        orientation = GradientDrawable.Orientation.LEFT_RIGHT;
                        break;
                    case 45:
                        orientation = GradientDrawable.Orientation.BL_TR;
                        break;
                    case 90:
                        orientation = GradientDrawable.Orientation.BOTTOM_TOP;
                        break;
                    case 135:
                        orientation = GradientDrawable.Orientation.BR_TL;
                        break;
                    case 180:
                        orientation = GradientDrawable.Orientation.RIGHT_LEFT;
                        break;
                    case 225:
                        orientation = GradientDrawable.Orientation.TR_BL;
                        break;
                    case 270:
                        orientation = GradientDrawable.Orientation.TOP_BOTTOM;
                        break;
                    case 315:
                        orientation = GradientDrawable.Orientation.TL_BR;
                        break;
                    }
                }
            }
            return orientation;
        }

        public static GradientDrawable init(@Nullable int[] colors, @Nullable Integer angle) {
            return colors != null ? new GradientDrawable(getOrientation(angle), colors) : new GradientDrawable();
        }

        @Override
        public void apply(Context context, GradientDrawable gradientDrawable) {
            if (null != centerX && null != centerY) {
                gradientDrawable.setGradientCenter(centerX, centerY);
            }

            if (null != gradientRadius) {
                gradientDrawable.setGradientRadius(gradientRadius);
            }

            if (!TextUtils.isEmpty(gradientType)) {
                switch (gradientType) {
                case LINEAR_GRADIENT:
                    gradientDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
                    break;
                case RADIAL_GRADIENT:
                    gradientDrawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
                    break;
                case SWEEP_GRADIENT:
                    gradientDrawable.setGradientType(GradientDrawable.SWEEP_GRADIENT);
                    break;
                }

            }
        }

        public GradientDrawable init(Context context) {
            int[] colors = null;
            if (centerColor != null) {
                colors = new int[3];
                colors[0] = loadColor(context, startColor);
                colors[1] = loadColor(context, centerColor);
                colors[2] = loadColor(context, endColor);
            } else {
                colors = new int[2];
                colors[0] = loadColor(context, startColor);
                colors[1] = loadColor(context, endColor);
            }

            return init(colors, angle);
        }
    }

    private static class Size extends GradientDrawableElement {

        @SerializedName("width")
        public String width;
        @SerializedName("height")
        public String height;

        @Override
        public void apply(Context context, GradientDrawable gradientDrawable) {
            gradientDrawable.setSize((int) ParseHelper.parseDimension(width, context),
                    (int) ParseHelper.parseDimension(height, context));
        }
    }

    private static class Stroke extends GradientDrawableElement {

        @SerializedName("width")
        public String width;
        @SerializedName("color")
        public JsonElement color;
        @SerializedName("dashWidth")
        public String dashWidth;
        @SerializedName("dashGap")
        public String dashGap;

        @Override
        public void apply(Context context, GradientDrawable gradientDrawable) {
            if (null == dashWidth) {
                gradientDrawable.setStroke((int) ParseHelper.parseDimension(width, context),
                        loadColor(context, color));
            } else if (null != dashWidth) {
                gradientDrawable.setStroke((int) ParseHelper.parseDimension(width, context),
                        loadColor(context, color), ParseHelper.parseDimension(dashWidth, context),
                        ParseHelper.parseDimension(dashGap, context));
            }
        }
    }

    private static class LayerListDrawableItem {

        @SerializedName("minLevel")
        public Integer minLevel;
        @SerializedName("maxLevel")
        public Integer maxLevel;
        @SerializedName("drawable")
        public JsonElement drawable;

        public void addItem(Context context, final LevelListDrawable levelListDrawable) {
            DrawableResourceProcessor<View> processor = new DrawableResourceProcessor<View>() {
                @Override
                public void setDrawable(View view, Drawable drawable) {
                    levelListDrawable.addLevel(minLevel, maxLevel, drawable);
                }
            };
        }
    }

    private static class ShapeDrawableJson {

        @SerializedName("shape")
        public String shape;
        @SerializedName("innerRadius")
        public String innerRadius;
        @SerializedName("innerRadiusRatio")
        public Float innerRadiusRatio;
        @SerializedName("thickness")
        public String thickness;
        @SerializedName("thicknessRatio")
        public Float thicknessRatio;
        @SerializedName("children")
        public JsonArray children;

        public GradientDrawable init(Context context) {
            ArrayList<GradientDrawableElement> elements = null;
            Gradient gradient = null;

            if (children != null && children.size() > 0) {
                elements = new ArrayList<>(children.size());
                for (JsonElement jsonElement : children) {
                    if (jsonElement.isJsonObject()) {
                        String typeKey = jsonElement.getAsJsonObject().getAsJsonPrimitive(TYPE).getAsString();
                        GradientDrawableElement element = null;
                        switch (typeKey) {
                        case TYPE_CORNERS:
                            element = sGson.fromJson(jsonElement, Corners.class);
                            break;
                        case TYPE_PADDING:
                            break;
                        case TYPE_SIZE:
                            element = sGson.fromJson(jsonElement, Size.class);
                            break;
                        case TYPE_SOLID:
                            element = sGson.fromJson(jsonElement, Solid.class);
                            break;
                        case TYPE_STROKE:
                            element = sGson.fromJson(jsonElement, Stroke.class);
                            break;
                        case TYPE_GRADIENT:
                            gradient = sGson.fromJson(jsonElement, Gradient.class);
                            element = gradient;
                            break;
                        }

                        if (null != element) {
                            elements.add(element);
                        }

                    }
                }
            }

            GradientDrawable gradientDrawable = (null != gradient) ? gradient.init(context)
                    : new GradientDrawable();

            if (!TextUtils.isEmpty(shape)) {
                int shapeInt = -1;
                switch (shape) {
                case SHAPE_RECTANGLE:
                    shapeInt = GradientDrawable.RECTANGLE;
                    break;
                case SHAPE_OVAL:
                    shapeInt = GradientDrawable.OVAL;
                    break;
                case SHAPE_LINE:
                    shapeInt = GradientDrawable.LINE;
                    break;
                case SHAPE_RING:
                    shapeInt = GradientDrawable.RING;
                    break;
                }

                if (-1 != shapeInt) {
                    gradientDrawable.setShape(shapeInt);
                }
            }
            if (null != elements) {
                for (GradientDrawableElement element : elements) {
                    element.apply(context, gradientDrawable);
                }
            }

            return gradientDrawable;
        }
    }

    private static class RippleDrawableJson {

        @SerializedName("color")
        @NonNull
        public JsonElement color;

        @SerializedName("mask")
        @Nullable
        public JsonElement mask;

        @SerializedName("content")
        @Nullable
        public JsonElement content;

        @SerializedName("defaultBackground")
        @Nullable
        public JsonElement defaultBackground;

        private transient ColorStateList colorStateList = null;
        private transient Drawable contentDrawable = null;
        private transient Drawable maskDrawable = null;
        private transient Drawable defaultBackgroundDrawable = null;

        @Nullable
        Drawable init(@NonNull Context context, @NonNull String attributeKey, @NonNull View view) {
            Drawable resultDrawable = null;
            ColorUtils.loadColor(context, color, new ValueCallback<Integer>() {
                @Override
                public void onReceiveValue(Integer value) {
                    int[][] states = new int[][] { new int[] {} };

                    int[] colors = new int[] { value };

                    colorStateList = new ColorStateList(states, colors);
                }
            }, new ValueCallback<ColorStateList>() {
                @Override
                public void onReceiveValue(ColorStateList value) {
                    colorStateList = value;
                }
            });

            if (null != content) {
                DrawableResourceProcessor contentDrawableProcessor = new DrawableResourceProcessor() {
                    @Override
                    public void setDrawable(View view, Drawable drawable) {
                        contentDrawable = drawable;
                    }
                };
                contentDrawableProcessor.handle(attributeKey, content, view);
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != colorStateList) {
                if (null != mask) {
                    DrawableResourceProcessor maskDrawableProcessor = new DrawableResourceProcessor() {
                        @Override
                        public void setDrawable(View view, Drawable drawable) {
                            maskDrawable = drawable;
                        }
                    };
                    maskDrawableProcessor.handle(attributeKey, mask, view);
                }

                resultDrawable = new RippleDrawable(colorStateList, contentDrawable, maskDrawable);
            } else if (null != defaultBackground) {
                DrawableResourceProcessor defaultDrawableProcessor = new DrawableResourceProcessor() {
                    @Override
                    public void setDrawable(View view, Drawable drawable) {
                        defaultBackgroundDrawable = drawable;
                    }
                };
                defaultDrawableProcessor.handle(attributeKey, defaultBackground, view);
                resultDrawable = defaultBackgroundDrawable;
            } else if (null != colorStateList && contentDrawable != null) {
                int pressedColor = colorStateList.getColorForState(new int[] { android.R.attr.state_pressed },
                        colorStateList.getDefaultColor());
                int focussedColor = colorStateList.getColorForState(new int[] { android.R.attr.state_focused },
                        pressedColor);
                ColorDrawable pressedColorDrawable = new ColorDrawable(pressedColor);
                ColorDrawable focussedColorDrawable = new ColorDrawable(focussedColor);
                StateListDrawable stateListDrawable = new StateListDrawable();
                stateListDrawable.addState(new int[] { android.R.attr.state_enabled, android.R.attr.state_pressed },
                        pressedColorDrawable);
                stateListDrawable.addState(new int[] { android.R.attr.state_enabled, android.R.attr.state_focused },
                        focussedColorDrawable);
                stateListDrawable.addState(new int[] { android.R.attr.state_enabled }, contentDrawable);
                resultDrawable = stateListDrawable;
            }
            return resultDrawable;
        }
    }
}