<commit_before>package com.snowble.android.verticalstepper;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

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

public class VerticalStepper extends ViewGroup {

    private Context context;
    private Resources resources;

    private List<View> stepViews;

    private boolean useSuggestedPadding;
    private int outerHorizontalPadding;
    private int outerVerticalPadding;

    private int iconDimension;
    private int iconMarginRight;
    private Paint iconBackgroundPaint;
    private RectF reuseRectIconBackground;
    private TextPaint iconTextPaint;
    private Rect reuseRectIconText;

    private TextPaint titleTextPaint;
    private Rect reuseRectTitleText;
    private float reuseBaselineTitle;
    private float reuseHeightTitle;
    private TextPaint summaryTextPaint;
    private float reuseBaselineSummary;
    private float reuseHeightSummary;

    private int touchViewHeight;
    private int touchViewBackground;

    public VerticalStepper(Context context) {
        super(context);
        init();
    }

    public VerticalStepper(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public VerticalStepper(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public VerticalStepper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    private void init() {
        init(null);
    }

    private void init(@Nullable AttributeSet attrs) {
        init(attrs, 0);
    }

    private void init(@Nullable AttributeSet attrs, int defStyleAttr) {
        init(attrs, defStyleAttr, 0);
    }

    private void init(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        setWillNotDraw(false);

        context = getContext();
        resources = getResources();

        initPropertiesFromAttrs(attrs, defStyleAttr, defStyleRes);
        initPadding();
        initIconProperties();
        initTitleProperties();
        initSummaryProperties();
        initTouchViewProperties();
    }

    private void initPropertiesFromAttrs(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        if (attrs != null) {
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerticalStepper,
                    defStyleAttr, defStyleRes);
            try {
                validateSuggestedPaddingUsage(a);
                useSuggestedPadding = a.getBoolean(R.styleable.VerticalStepper_useSuggestedPadding, false);
            } finally {
                a.recycle();
            }
        }
    }

    private void validateSuggestedPaddingUsage(TypedArray a) {
        if (a.hasValue(R.styleable.VerticalStepper_useSuggestedPadding)
                && (getPaddingLeft() != 0
                || getPaddingTop() != 0
                || getPaddingRight() != 0
                || getPaddingBottom() != 0)) {
            throw new IllegalStateException("padding values must be zero when useSuggestedPadding is true.");
        }
    }

    private void initPadding() {
        if (useSuggestedPadding) {
            outerHorizontalPadding = resources.getDimensionPixelSize(R.dimen.outer_padding_horizontal);
            outerVerticalPadding = resources.getDimensionPixelSize(R.dimen.outer_padding_vertical);
        } else {
            outerHorizontalPadding = 0;
            outerVerticalPadding = 0;
        }
    }

    private void initIconProperties() {
        initIconDimension();
        initIconMargins();
        initIconBackground();
        initIconTextPaint();
        initIconRectsForReuse();
    }

    private void initIconDimension() {
        iconDimension = resources.getDimensionPixelSize(R.dimen.icon_diameter);
    }

    private void initIconMargins() {
        iconMarginRight = resources.getDimensionPixelSize(R.dimen.icon_margin_right);
    }

    private void initIconBackground() {
        //noinspection deprecation
        int defaultColor = resources.getColor(R.color.bg_icon);
        int iconBackground = getResolvedAttributeData(R.attr.colorPrimary, defaultColor, true);
        iconBackgroundPaint = new Paint();
        iconBackgroundPaint.setColor(iconBackground);
        iconBackgroundPaint.setAntiAlias(true);
    }

    private void initIconTextPaint() {
        iconTextPaint = createTextPaint(R.color.white, R.dimen.icon_font_size);
    }

    private void initIconRectsForReuse() {
        reuseRectIconBackground = new RectF(0, 0, iconDimension, iconDimension);
        reuseRectIconText = new Rect();
    }

    private void initTitleProperties() {
        initTitleTextPaint();
        initTitleRectsForReuse();
    }

    private void initTitleTextPaint() {
        titleTextPaint = createTextPaint(R.color.title_color, R.dimen.title_font_size);
        titleTextPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
    }

    private void initTitleRectsForReuse() {
        reuseRectTitleText = new Rect();
    }

    private void initSummaryProperties() {
        initSummaryTextPaint();
    }

    private void initSummaryTextPaint() {
        summaryTextPaint = createTextPaint(R.color.summary_color, R.dimen.summary_font_size);
    }

    private void initTouchViewProperties() {
        touchViewHeight = resources.getDimensionPixelSize(R.dimen.touch_height);
        touchViewBackground = getResolvedAttributeData(R.attr.selectableItemBackground, 0, false);
    }

    private int getResolvedAttributeData(int attr, int defaultData, boolean resolveRefs) {
        TypedValue value = new TypedValue();
        context.getTheme().resolveAttribute(attr, value, resolveRefs);
        int resolvedAttributeData;
        if (value.type != TypedValue.TYPE_NULL) {
            resolvedAttributeData = value.data;
        } else {
            resolvedAttributeData = defaultData;
        }
        return resolvedAttributeData;
    }

    private TextPaint createTextPaint(int colorRes, int fontDimenRes) {
        TextPaint textPaint = new TextPaint();
        setTextColor(textPaint, colorRes);
        textPaint.setAntiAlias(true);
        int titleTextSize = resources.getDimensionPixelSize(fontDimenRes);
        textPaint.setTextSize(titleTextSize);
        return textPaint;
    }

    private void setTextColor(TextPaint paint, int colorRes) {
        int color;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            color = resources.getColor(colorRes, context.getTheme());
        } else {
            //noinspection deprecation
            color = resources.getColor(colorRes);
        }
        paint.setColor(color);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        int childCount = getChildCount();
        stepViews = new ArrayList<>(childCount);
        for (int i = 0; i < childCount; i++) {
            initStepView(getChildAt(i));
        }

        for (View v : stepViews) {
            InternalTouchView touchView = getTouchView(v);
            initTouchView(touchView);
        }
    }

    private void initStepView(final View stepView) {
        stepView.setVisibility(View.GONE);
        stepViews.add(stepView);

        createAndAttachTouchView(stepView);
    }

    private void createAndAttachTouchView(final View stepView) {
        getInternalLayoutParams(stepView).touchView = new InternalTouchView(context);
    }

    private void initTouchView(InternalTouchView touchView) {
        touchView.setBackgroundResource(touchViewBackground);
        // TODO See if the anonymous inner class can be avoided
        touchView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Make step view visible.
            }
        });
        addView(touchView);
        LayoutParams lp = (LayoutParams) touchView.getLayoutParams();
        lp.width = LayoutParams.MATCH_PARENT;
        lp.height = touchViewHeight;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int wSizeFromSpec = MeasureSpec.getSize(widthMeasureSpec);
        int wModeFromSpec = MeasureSpec.getMode(widthMeasureSpec);

        int hSizeFromSpec = MeasureSpec.getSize(heightMeasureSpec);
        int hModeFromSpec = MeasureSpec.getMode(heightMeasureSpec);

        int width;
        int height;

        boolean measureWidth = wModeFromSpec != MeasureSpec.EXACTLY;
        boolean measureHeight = hModeFromSpec != MeasureSpec.EXACTLY;

        if (measureWidth) {
            if (useSuggestedPadding) {
                width = outerHorizontalPadding;
            } else {
                width = getPaddingLeft() + getPaddingRight();
            }
        } else {
            width = wSizeFromSpec;
        }

        if (measureHeight) {
            if (useSuggestedPadding) {
                height = outerVerticalPadding;
            } else {
                height = getPaddingTop() + getPaddingBottom();
            }
        } else {
            height = hSizeFromSpec;
        }

        for (View v : stepViews) {
            if (measureWidth) {
                int stepWidth = measureStepWidth(v);
                width = Math.max(width, stepWidth);
            }

            if (measureHeight) {
                int stepHeight = measureStepHeight(v);
                height += stepHeight;
            }

            // TODO Measure active child and add that to our measurements
        }
        width += outerHorizontalPadding;
        height += outerVerticalPadding;

        width = Math.max(width, getSuggestedMinimumWidth());
        height = Math.max(height, getSuggestedMinimumHeight());

        width = resolveSize(width, widthMeasureSpec);
        height = resolveSize(height, heightMeasureSpec);

        for (View v : stepViews) {
            int wms = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
            int hms = MeasureSpec.makeMeasureSpec(touchViewHeight, MeasureSpec.EXACTLY);
            getTouchView(v).measure(wms, hms);
        }

        setMeasuredDimension(width, height);
    }

    private int measureStepHeight(View v) {
        int stepHeight = 0;
        measureTitleHeight(getInternalLayoutParams(v).title);
        measureSummaryHeight();
        int textTotalHeight = (int) (reuseHeightTitle + reuseHeightSummary);
        int stepDecoratorHeight = Math.max(iconDimension, textTotalHeight);

        stepHeight += stepDecoratorHeight;
        return stepHeight;
    }

    private int measureStepWidth(View v) {
        int stepWidth = iconDimension;
        stepWidth += iconMarginRight;

        float titleWidth = measureTitleWidth(v);
        float summaryWidth = measureSummaryWidth(v);
        stepWidth += Math.max(titleWidth, summaryWidth);
        return stepWidth;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (View v : stepViews) {
            // TODO Update l,t,r,b based on translations
            InternalTouchView touchView = getTouchView(v);
            touchView.layout(left, top, right - left, top + touchView.getMeasuredHeight());
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        if (useSuggestedPadding) {
            canvas.translate(outerHorizontalPadding, outerVerticalPadding);
        } else {
            canvas.translate(getPaddingBottom(), getPaddingTop());
        }
        for (int i = 0; i < stepViews.size(); i++) {
            canvas.save();

            View stepView = stepViews.get(i);

            int stepNumber = i + 1;
            canvas.save();
            drawIcon(canvas, stepNumber);
            canvas.restore();

            canvas.save();
            drawText(canvas, getInternalLayoutParams(stepView));
            canvas.restore();

            canvas.restore();
        }
        canvas.restore();
    }

    private void drawIcon(Canvas canvas, int stepNumber) {
        drawIconBackground(canvas);
        drawIconText(canvas, stepNumber);
    }

    private void drawIconBackground(Canvas canvas) {
        canvas.drawArc(reuseRectIconBackground, 0f, 360f, true, iconBackgroundPaint);
    }

    private void drawIconText(Canvas canvas, int stepNumber) {
        String stepNumberString = String.format(Locale.getDefault(), "%d", stepNumber);

        float width = iconTextPaint.measureText(stepNumberString);
        float centeredTextX = (iconDimension / 2) - (width / 2);

        iconTextPaint.getTextBounds(stepNumberString, 0, 1, reuseRectIconText);
        float centeredTextY = (iconDimension / 2) + (reuseRectIconText.height() / 2);

        canvas.drawText(stepNumberString, centeredTextX, centeredTextY, iconTextPaint);
    }

    private void drawText(Canvas canvas, LayoutParams lp) {
        measureTitleHeight(lp.title);
        measureSummaryHeight();

        canvas.translate(iconDimension + iconMarginRight, 0);
        canvas.drawText(lp.title, 0, reuseBaselineTitle, titleTextPaint);
        if (!TextUtils.isEmpty(lp.summary)) {
            canvas.translate(0, reuseHeightTitle);
            canvas.drawText(lp.summary, 0, reuseBaselineSummary, summaryTextPaint);
        }
        // TODO Handle optional case
    }

    private float measureTitleWidth(View v) {
        float titleWidth = 0f;
        String title = getInternalLayoutParams(v).title;
        if (!TextUtils.isEmpty(title)) {
            titleWidth = titleTextPaint.measureText(title);
        }
        return titleWidth;
    }

    private float measureSummaryWidth(View v) {
        float summaryWidth = 0f;
        String summary = getInternalLayoutParams(v).summary;
        // TODO Handle case when the v is active
        if (!TextUtils.isEmpty(summary)) {
            summaryWidth = titleTextPaint.measureText(summary);
        }
        return summaryWidth;
    }

    private void measureTitleHeight(String title) {
        reuseBaselineTitle = getTitleBaseline(title);
        reuseHeightTitle = reuseBaselineTitle + titleTextPaint.getFontMetrics().bottom;
    }

    private float getTitleBaseline(String title) {
        titleTextPaint.getTextBounds(title, 0, 1, reuseRectTitleText);
        return (iconDimension / 2) + (reuseRectTitleText.height() / 2);
    }

    private void measureSummaryHeight() {
        reuseBaselineSummary = getSummaryBaseline();
        reuseHeightSummary = reuseBaselineSummary + summaryTextPaint.getFontMetrics().bottom;
    }

    private float getSummaryBaseline() {
        return -summaryTextPaint.getFontMetrics().ascent;
    }

    private static InternalTouchView getTouchView(View stepView) {
        return getInternalLayoutParams(stepView).touchView;
    }

    private static LayoutParams getInternalLayoutParams(View stepView) {
        return (LayoutParams) stepView.getLayoutParams();
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(context, attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    public static class LayoutParams extends MarginLayoutParams {
        InternalTouchView touchView;

        @NonNull
        String title;
        @Nullable
        String summary;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.VerticalStepper_Layout);
            try {
                //noinspection ConstantConditions
                title = a.getString(R.styleable.VerticalStepper_Layout_step_title);
                summary = a.getString(R.styleable.VerticalStepper_Layout_step_summary);
            } finally {
                a.recycle();
            }
            if (TextUtils.isEmpty(title)) {
                throw new IllegalArgumentException("step_title cannot be empty.");
            }
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }

    private static class InternalTouchView extends View {
        public InternalTouchView(Context context) {
            super(context);
        }
    }
}
<commit_msg>Make initReuse*() methods more generic<commit_after>package com.snowble.android.verticalstepper;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

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

public class VerticalStepper extends ViewGroup {

    private Context context;
    private Resources resources;

    private List<View> stepViews;

    private boolean useSuggestedPadding;
    private int outerHorizontalPadding;
    private int outerVerticalPadding;

    private int iconDimension;
    private int iconMarginRight;
    private Paint iconBackgroundPaint;
    private RectF reuseRectIconBackground;
    private TextPaint iconTextPaint;
    private Rect reuseRectIconText;

    private TextPaint titleTextPaint;
    private Rect reuseRectTitleText;
    private float reuseBaselineTitle;
    private float reuseHeightTitle;
    private TextPaint summaryTextPaint;
    private float reuseBaselineSummary;
    private float reuseHeightSummary;

    private int touchViewHeight;
    private int touchViewBackground;

    public VerticalStepper(Context context) {
        super(context);
        init();
    }

    public VerticalStepper(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public VerticalStepper(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public VerticalStepper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    private void init() {
        init(null);
    }

    private void init(@Nullable AttributeSet attrs) {
        init(attrs, 0);
    }

    private void init(@Nullable AttributeSet attrs, int defStyleAttr) {
        init(attrs, defStyleAttr, 0);
    }

    private void init(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        setWillNotDraw(false);

        context = getContext();
        resources = getResources();

        initPropertiesFromAttrs(attrs, defStyleAttr, defStyleRes);
        initPadding();
        initIconProperties();
        initTitleProperties();
        initSummaryProperties();
        initTouchViewProperties();
    }

    private void initPropertiesFromAttrs(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        if (attrs != null) {
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerticalStepper,
                    defStyleAttr, defStyleRes);
            try {
                validateSuggestedPaddingUsage(a);
                useSuggestedPadding = a.getBoolean(R.styleable.VerticalStepper_useSuggestedPadding, false);
            } finally {
                a.recycle();
            }
        }
    }

    private void validateSuggestedPaddingUsage(TypedArray a) {
        if (a.hasValue(R.styleable.VerticalStepper_useSuggestedPadding)
                && (getPaddingLeft() != 0
                || getPaddingTop() != 0
                || getPaddingRight() != 0
                || getPaddingBottom() != 0)) {
            throw new IllegalStateException("padding values must be zero when useSuggestedPadding is true.");
        }
    }

    private void initPadding() {
        if (useSuggestedPadding) {
            outerHorizontalPadding = resources.getDimensionPixelSize(R.dimen.outer_padding_horizontal);
            outerVerticalPadding = resources.getDimensionPixelSize(R.dimen.outer_padding_vertical);
        } else {
            outerHorizontalPadding = 0;
            outerVerticalPadding = 0;
        }
    }

    private void initIconProperties() {
        initIconDimension();
        initIconMargins();
        initIconBackground();
        initIconTextPaint();
        initIconReuseObjects();
    }

    private void initIconDimension() {
        iconDimension = resources.getDimensionPixelSize(R.dimen.icon_diameter);
    }

    private void initIconMargins() {
        iconMarginRight = resources.getDimensionPixelSize(R.dimen.icon_margin_right);
    }

    private void initIconBackground() {
        //noinspection deprecation
        int defaultColor = resources.getColor(R.color.bg_icon);
        int iconBackground = getResolvedAttributeData(R.attr.colorPrimary, defaultColor, true);
        iconBackgroundPaint = new Paint();
        iconBackgroundPaint.setColor(iconBackground);
        iconBackgroundPaint.setAntiAlias(true);
    }

    private void initIconTextPaint() {
        iconTextPaint = createTextPaint(R.color.white, R.dimen.icon_font_size);
    }

    private void initIconReuseObjects() {
        reuseRectIconBackground = new RectF(0, 0, iconDimension, iconDimension);
        reuseRectIconText = new Rect();
    }

    private void initTitleProperties() {
        initTitleTextPaint();
        initTitleReuseObjects();
    }

    private void initTitleTextPaint() {
        titleTextPaint = createTextPaint(R.color.title_color, R.dimen.title_font_size);
        titleTextPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
    }

    private void initTitleReuseObjects() {
        reuseRectTitleText = new Rect();
    }

    private void initSummaryProperties() {
        initSummaryTextPaint();
    }

    private void initSummaryTextPaint() {
        summaryTextPaint = createTextPaint(R.color.summary_color, R.dimen.summary_font_size);
    }

    private void initTouchViewProperties() {
        touchViewHeight = resources.getDimensionPixelSize(R.dimen.touch_height);
        touchViewBackground = getResolvedAttributeData(R.attr.selectableItemBackground, 0, false);
    }

    private int getResolvedAttributeData(int attr, int defaultData, boolean resolveRefs) {
        TypedValue value = new TypedValue();
        context.getTheme().resolveAttribute(attr, value, resolveRefs);
        int resolvedAttributeData;
        if (value.type != TypedValue.TYPE_NULL) {
            resolvedAttributeData = value.data;
        } else {
            resolvedAttributeData = defaultData;
        }
        return resolvedAttributeData;
    }

    private TextPaint createTextPaint(int colorRes, int fontDimenRes) {
        TextPaint textPaint = new TextPaint();
        setTextColor(textPaint, colorRes);
        textPaint.setAntiAlias(true);
        int titleTextSize = resources.getDimensionPixelSize(fontDimenRes);
        textPaint.setTextSize(titleTextSize);
        return textPaint;
    }

    private void setTextColor(TextPaint paint, int colorRes) {
        int color;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            color = resources.getColor(colorRes, context.getTheme());
        } else {
            //noinspection deprecation
            color = resources.getColor(colorRes);
        }
        paint.setColor(color);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        int childCount = getChildCount();
        stepViews = new ArrayList<>(childCount);
        for (int i = 0; i < childCount; i++) {
            initStepView(getChildAt(i));
        }

        for (View v : stepViews) {
            InternalTouchView touchView = getTouchView(v);
            initTouchView(touchView);
        }
    }

    private void initStepView(final View stepView) {
        stepView.setVisibility(View.GONE);
        stepViews.add(stepView);

        createAndAttachTouchView(stepView);
    }

    private void createAndAttachTouchView(final View stepView) {
        getInternalLayoutParams(stepView).touchView = new InternalTouchView(context);
    }

    private void initTouchView(InternalTouchView touchView) {
        touchView.setBackgroundResource(touchViewBackground);
        // TODO See if the anonymous inner class can be avoided
        touchView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Make step view visible.
            }
        });
        addView(touchView);
        LayoutParams lp = (LayoutParams) touchView.getLayoutParams();
        lp.width = LayoutParams.MATCH_PARENT;
        lp.height = touchViewHeight;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int wSizeFromSpec = MeasureSpec.getSize(widthMeasureSpec);
        int wModeFromSpec = MeasureSpec.getMode(widthMeasureSpec);

        int hSizeFromSpec = MeasureSpec.getSize(heightMeasureSpec);
        int hModeFromSpec = MeasureSpec.getMode(heightMeasureSpec);

        int width;
        int height;

        boolean measureWidth = wModeFromSpec != MeasureSpec.EXACTLY;
        boolean measureHeight = hModeFromSpec != MeasureSpec.EXACTLY;

        if (measureWidth) {
            if (useSuggestedPadding) {
                width = outerHorizontalPadding;
            } else {
                width = getPaddingLeft() + getPaddingRight();
            }
        } else {
            width = wSizeFromSpec;
        }

        if (measureHeight) {
            if (useSuggestedPadding) {
                height = outerVerticalPadding;
            } else {
                height = getPaddingTop() + getPaddingBottom();
            }
        } else {
            height = hSizeFromSpec;
        }

        for (View v : stepViews) {
            if (measureWidth) {
                int stepWidth = measureStepWidth(v);
                width = Math.max(width, stepWidth);
            }

            if (measureHeight) {
                int stepHeight = measureStepHeight(v);
                height += stepHeight;
            }

            // TODO Measure active child and add that to our measurements
        }
        width += outerHorizontalPadding;
        height += outerVerticalPadding;

        width = Math.max(width, getSuggestedMinimumWidth());
        height = Math.max(height, getSuggestedMinimumHeight());

        width = resolveSize(width, widthMeasureSpec);
        height = resolveSize(height, heightMeasureSpec);

        for (View v : stepViews) {
            int wms = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
            int hms = MeasureSpec.makeMeasureSpec(touchViewHeight, MeasureSpec.EXACTLY);
            getTouchView(v).measure(wms, hms);
        }

        setMeasuredDimension(width, height);
    }

    private int measureStepHeight(View v) {
        int stepHeight = 0;
        measureTitleHeight(getInternalLayoutParams(v).title);
        measureSummaryHeight();
        int textTotalHeight = (int) (reuseHeightTitle + reuseHeightSummary);
        int stepDecoratorHeight = Math.max(iconDimension, textTotalHeight);

        stepHeight += stepDecoratorHeight;
        return stepHeight;
    }

    private int measureStepWidth(View v) {
        int stepWidth = iconDimension;
        stepWidth += iconMarginRight;

        float titleWidth = measureTitleWidth(v);
        float summaryWidth = measureSummaryWidth(v);
        stepWidth += Math.max(titleWidth, summaryWidth);
        return stepWidth;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (View v : stepViews) {
            // TODO Update l,t,r,b based on translations
            InternalTouchView touchView = getTouchView(v);
            touchView.layout(left, top, right - left, top + touchView.getMeasuredHeight());
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        if (useSuggestedPadding) {
            canvas.translate(outerHorizontalPadding, outerVerticalPadding);
        } else {
            canvas.translate(getPaddingBottom(), getPaddingTop());
        }
        for (int i = 0; i < stepViews.size(); i++) {
            canvas.save();

            View stepView = stepViews.get(i);

            int stepNumber = i + 1;
            canvas.save();
            drawIcon(canvas, stepNumber);
            canvas.restore();

            canvas.save();
            drawText(canvas, getInternalLayoutParams(stepView));
            canvas.restore();

            canvas.restore();
        }
        canvas.restore();
    }

    private void drawIcon(Canvas canvas, int stepNumber) {
        drawIconBackground(canvas);
        drawIconText(canvas, stepNumber);
    }

    private void drawIconBackground(Canvas canvas) {
        canvas.drawArc(reuseRectIconBackground, 0f, 360f, true, iconBackgroundPaint);
    }

    private void drawIconText(Canvas canvas, int stepNumber) {
        String stepNumberString = String.format(Locale.getDefault(), "%d", stepNumber);

        float width = iconTextPaint.measureText(stepNumberString);
        float centeredTextX = (iconDimension / 2) - (width / 2);

        iconTextPaint.getTextBounds(stepNumberString, 0, 1, reuseRectIconText);
        float centeredTextY = (iconDimension / 2) + (reuseRectIconText.height() / 2);

        canvas.drawText(stepNumberString, centeredTextX, centeredTextY, iconTextPaint);
    }

    private void drawText(Canvas canvas, LayoutParams lp) {
        measureTitleHeight(lp.title);
        measureSummaryHeight();

        canvas.translate(iconDimension + iconMarginRight, 0);
        canvas.drawText(lp.title, 0, reuseBaselineTitle, titleTextPaint);
        if (!TextUtils.isEmpty(lp.summary)) {
            canvas.translate(0, reuseHeightTitle);
            canvas.drawText(lp.summary, 0, reuseBaselineSummary, summaryTextPaint);
        }
        // TODO Handle optional case
    }

    private float measureTitleWidth(View v) {
        float titleWidth = 0f;
        String title = getInternalLayoutParams(v).title;
        if (!TextUtils.isEmpty(title)) {
            titleWidth = titleTextPaint.measureText(title);
        }
        return titleWidth;
    }

    private float measureSummaryWidth(View v) {
        float summaryWidth = 0f;
        String summary = getInternalLayoutParams(v).summary;
        // TODO Handle case when the v is active
        if (!TextUtils.isEmpty(summary)) {
            summaryWidth = titleTextPaint.measureText(summary);
        }
        return summaryWidth;
    }

    private void measureTitleHeight(String title) {
        reuseBaselineTitle = getTitleBaseline(title);
        reuseHeightTitle = reuseBaselineTitle + titleTextPaint.getFontMetrics().bottom;
    }

    private float getTitleBaseline(String title) {
        titleTextPaint.getTextBounds(title, 0, 1, reuseRectTitleText);
        return (iconDimension / 2) + (reuseRectTitleText.height() / 2);
    }

    private void measureSummaryHeight() {
        reuseBaselineSummary = getSummaryBaseline();
        reuseHeightSummary = reuseBaselineSummary + summaryTextPaint.getFontMetrics().bottom;
    }

    private float getSummaryBaseline() {
        return -summaryTextPaint.getFontMetrics().ascent;
    }

    private static InternalTouchView getTouchView(View stepView) {
        return getInternalLayoutParams(stepView).touchView;
    }

    private static LayoutParams getInternalLayoutParams(View stepView) {
        return (LayoutParams) stepView.getLayoutParams();
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(context, attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    public static class LayoutParams extends MarginLayoutParams {
        InternalTouchView touchView;

        @NonNull
        String title;
        @Nullable
        String summary;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.VerticalStepper_Layout);
            try {
                //noinspection ConstantConditions
                title = a.getString(R.styleable.VerticalStepper_Layout_step_title);
                summary = a.getString(R.styleable.VerticalStepper_Layout_step_summary);
            } finally {
                a.recycle();
            }
            if (TextUtils.isEmpty(title)) {
                throw new IllegalArgumentException("step_title cannot be empty.");
            }
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }

    private static class InternalTouchView extends View {
        public InternalTouchView(Context context) {
            super(context);
        }
    }
}

{'fraction_non_alphanumeric': 0.0850741576886806, 'fraction_numerical': 0.002000400080016003, 'mean_word_length': 2.613962614892079, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '423490', 'n_tokens_mistral': 9311, 'n_tokens_neox': 8944, 'n_words': 1992}