sjwall/MaterialTapTargetPrompt

View on GitHub
library/src/main/java/uk/co/samuelwall/materialtaptargetprompt/extras/PromptOptions.java

Summary

Maintainability
A
2 hrs
Test Coverage
/*
 * Copyright (C) 2017 Samuel Wall
 *
 * 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 uk.co.samuelwall.materialtaptargetprompt.extras;

import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.annotation.ColorInt;
import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;

import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import uk.co.samuelwall.materialtaptargetprompt.R;
import uk.co.samuelwall.materialtaptargetprompt.ResourceFinder;
import uk.co.samuelwall.materialtaptargetprompt.extras.backgrounds.CirclePromptBackground;
import uk.co.samuelwall.materialtaptargetprompt.extras.focals.CirclePromptFocal;

/**
 * Contains all the settings for creating a prompt.
 *
 * @param <T> The subclass that extends this.
 */
@SuppressWarnings("unchecked")
public class PromptOptions<T extends PromptOptions>
{
    /**
     * The {@link ResourceFinder} used to find views and resources.
     */
    private ResourceFinder mResourceFinder;

    /**
     * Has the target been set successfully.
     */
    private boolean mTargetSet;

    /**
     * The view to place the prompt around.
     */
    @Nullable private View mTargetView;

    /**
     * The left and top positioning for the focal centre point.
     */
    @Nullable private PointF mTargetPosition;

    /**
     * The primary text to display.
     */
    @Nullable private CharSequence mPrimaryText;

    /**
     * The secondary text to display.
     */
    @Nullable private CharSequence mSecondaryText;

    /**
     * The colour for the primary text.
     */
    @ColorInt private int mPrimaryTextColour = Color.WHITE;

    /**
     * The colour for the secondary text.
     */
    @ColorInt private int mSecondaryTextColour = Color.argb(179, 255, 255, 255);

    /**
     * The colour for the prompt background.
     */
    @ColorInt private int mBackgroundColour = Color.argb(244, 63, 81, 181);

    /**
     * The colour for the prompt focal.
     */
    @ColorInt private int mFocalColour = Color.WHITE;

    /**
     * The circle focal implementation radius.
     */
    private float mFocalRadius;

    /**
     * The primary text font size.
     */
    private float mPrimaryTextSize;

    /**
     * The secondary text font size.
     */
    private float mSecondaryTextSize;

    /**
     * The maximum width to allow the text to be.
     */
    private float mMaxTextWidth;

    /**
     * The distance between the text and the background edge.
     */
    private float mTextPadding;

    /**
     * The distance between the focal edge and the text.
     */
    private float mFocalPadding;

    /**
     * The interpolator to use for animations.
     */
    @Nullable private Interpolator mAnimationInterpolator;

    /**
     * The drawable to use to render the target.
     */
    @Nullable private Drawable mIconDrawable;

    /**
     * Should the back button press dismiss the prompt.
     */
    private boolean mBackButtonDismissEnabled = true;

    /**
     * Indicates how drawing area should be placed regarding to system status bar
     */
    private boolean mIgnoreStatusBar = false;

    /**
     * Listener for when the prompt state changes.
     */
    @Nullable private MaterialTapTargetPrompt.PromptStateChangeListener mPromptStateChangeListener;

    /**
     * Additional listener that can be set by other package classes for handling e.g. sequences of
     * prompts.
     */
    @Nullable private MaterialTapTargetPrompt.PromptStateChangeListener mSequencePromptStateChangeListener;


    private boolean mCaptureTouchEventOnFocal;
    private float mTextSeparation;
    private boolean mAutoDismiss = true;
    private boolean mAutoFinish = true;
    private boolean mCaptureTouchEventOutsidePrompt;
    @Nullable private Typeface mPrimaryTextTypeface, mSecondaryTextTypeface;
    @Nullable private String mContentDescription;
    private int mPrimaryTextTypefaceStyle, mSecondaryTextTypefaceStyle;
    @Nullable private ColorStateList mIconDrawableTintList = null;
    @Nullable private PorterDuff.Mode mIconDrawableTintMode = PorterDuff.Mode.MULTIPLY;
    private boolean mHasIconDrawableTint;
    private int mIconDrawableColourFilter;
    @Nullable private View mTargetRenderView;
    private boolean mIdleAnimationEnabled = true;
    private int mPrimaryTextGravity = Gravity.START, mSecondaryTextGravity = Gravity.START;
    @Nullable private View mClipToView;

    /**
     * The shape to render for the prompt background.
     */
    @NonNull private PromptBackground mPromptBackground = new CirclePromptBackground();

    /**
     * The shape to render for the prompt focal.
     */
    @NonNull private PromptFocal mPromptFocal = new CirclePromptFocal();

    /**
     * The renderer for drawing the prompt text.
     */
    @NonNull private PromptText mPromptText = new PromptText();

    /**
     * Constructor.
     *
     * @param resourceFinder The resource finder implementation to use to find resources.
     */
    public PromptOptions(@NonNull final ResourceFinder resourceFinder)
    {
        mResourceFinder = resourceFinder;
        final float density = mResourceFinder.getResources().getDisplayMetrics().density;
        mFocalRadius =       density * 44;
        mPrimaryTextSize =   density * 22;
        mSecondaryTextSize = density * 18;
        mMaxTextWidth =      density * 400;
        mTextPadding =       density * 40;
        mFocalPadding =      density * 20;
        mTextSeparation =    density * 16;
    }

    /**
     * Loads the supplied theme into the prompt overwriting any previously set values if they are set in the theme.
     *
     * @param themeResId The resource id for the theme.
     */
    public void load(@StyleRes int themeResId)
    {
        //Attempt to load the theme from the activity theme
        if (themeResId == 0)
        {
            final TypedValue outValue = new TypedValue();
            mResourceFinder.getTheme().resolveAttribute(R.attr.MaterialTapTargetPromptTheme, outValue, true);
            themeResId = outValue.resourceId;
        }

        final TypedArray a = mResourceFinder.obtainStyledAttributes(themeResId, R.styleable.PromptView);
        mPrimaryTextColour = a.getColor(R.styleable.PromptView_mttp_primaryTextColour, mPrimaryTextColour);
        mSecondaryTextColour = a.getColor(R.styleable.PromptView_mttp_secondaryTextColour, mSecondaryTextColour);
        mPrimaryText = a.getString(R.styleable.PromptView_mttp_primaryText);
        mSecondaryText = a.getString(R.styleable.PromptView_mttp_secondaryText);
        mBackgroundColour = a.getColor(R.styleable.PromptView_mttp_backgroundColour, mBackgroundColour);
        mFocalColour = a.getColor(R.styleable.PromptView_mttp_focalColour, mFocalColour);
        mFocalRadius = a.getDimension(R.styleable.PromptView_mttp_focalRadius, mFocalRadius);
        mPrimaryTextSize = a.getDimension(R.styleable.PromptView_mttp_primaryTextSize, mPrimaryTextSize);
        mSecondaryTextSize = a.getDimension(R.styleable.PromptView_mttp_secondaryTextSize, mSecondaryTextSize);
        mMaxTextWidth = a.getDimension(R.styleable.PromptView_mttp_maxTextWidth, mMaxTextWidth);
        mTextPadding = a.getDimension(R.styleable.PromptView_mttp_textPadding, mTextPadding);
        mFocalPadding = a.getDimension(R.styleable.PromptView_mttp_focalToTextPadding, mFocalPadding);
        mTextSeparation = a.getDimension(R.styleable.PromptView_mttp_textSeparation, mTextSeparation);
        mAutoDismiss = a.getBoolean(R.styleable.PromptView_mttp_autoDismiss, mAutoDismiss);
        mAutoFinish = a.getBoolean(R.styleable.PromptView_mttp_autoFinish, mAutoFinish);
        mCaptureTouchEventOutsidePrompt = a.getBoolean(R.styleable.PromptView_mttp_captureTouchEventOutsidePrompt, mCaptureTouchEventOutsidePrompt);
        mCaptureTouchEventOnFocal = a.getBoolean(R.styleable.PromptView_mttp_captureTouchEventOnFocal, mCaptureTouchEventOnFocal);
        mPrimaryTextTypefaceStyle = a.getInt(R.styleable.PromptView_mttp_primaryTextStyle, mPrimaryTextTypefaceStyle);
        mSecondaryTextTypefaceStyle = a.getInt(R.styleable.PromptView_mttp_secondaryTextStyle, mSecondaryTextTypefaceStyle);
        mPrimaryTextTypeface = PromptUtils.setTypefaceFromAttrs(a.getString(R.styleable.PromptView_mttp_primaryTextFontFamily), a.getInt(R.styleable.PromptView_mttp_primaryTextTypeface, 0), mPrimaryTextTypefaceStyle);
        mSecondaryTextTypeface = PromptUtils.setTypefaceFromAttrs(a.getString(R.styleable.PromptView_mttp_secondaryTextFontFamily), a.getInt(R.styleable.PromptView_mttp_secondaryTextTypeface, 0), mSecondaryTextTypefaceStyle);
        mContentDescription = a.getString(R.styleable.PromptView_mttp_contentDescription);

        mIconDrawableColourFilter = a.getColor(R.styleable.PromptView_mttp_iconColourFilter, mBackgroundColour);
        mIconDrawableTintList = a.getColorStateList(R.styleable.PromptView_mttp_iconTint);
        mIconDrawableTintMode = PromptUtils.parseTintMode(a.getInt(R.styleable.PromptView_mttp_iconTintMode, -1), mIconDrawableTintMode);
        mHasIconDrawableTint = true;

        final int targetId = a.getResourceId(R.styleable.PromptView_mttp_target, 0);
        a.recycle();

        if (targetId != 0)
        {
            mTargetView = mResourceFinder.findViewById(targetId);
            if (mTargetView != null)
            {
                mTargetSet = true;
            }
        }
        final View contentView = mResourceFinder.findViewById(android.R.id.content);
        if (contentView != null)
        {
            mClipToView = (View) contentView.getParent();
        }
    }

    /**
     * Get the resource finder being used.
     *
     * @return The resource finder being used.
     */
    @NonNull
    public ResourceFinder getResourceFinder()
    {
        return mResourceFinder;
    }

    /**
     * Set the view for the prompt to focus on.
     *
     * @param target The view that the prompt will highlight.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTarget(@Nullable final View target)
    {
        mTargetView = target;
        mTargetPosition = null;
        mTargetSet = mTargetView != null;
        return (T) this;
    }

    /**
     * Set the view for the prompt to focus on using the given resource id.
     *
     * @param target The view that the prompt will highlight.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTarget(@IdRes final int target)
    {
        mTargetView = mResourceFinder.findViewById(target);
        mTargetPosition = null;
        mTargetSet = mTargetView != null;
        return (T) this;
    }

    /**
     * Gets the view that the prompt is targeting.
     *
     * @return The target view or null if not set or targeting a position.
     */
    @Nullable
    public View getTargetView()
    {
        return mTargetView;
    }

    /**
     * Set the centre point as a screen position
     *
     * @param left Centre point from screen left
     * @param top  Centre point from screen top
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTarget(final float left, final float top)
    {
        mTargetView = null;
        mTargetPosition = new PointF(left, top);
        mTargetSet = true;
        return (T) this;
    }

    /**
     * Get the position on the screen that is being targeted.
     *
     * @return The target position or null if targeting a view.
     */
    @Nullable
    public PointF getTargetPosition()
    {
        return mTargetPosition;
    }

    /**
     * Change the view that is rendered as the target.
     * By default the view from {@link #setTarget(View)} is rendered as the target.
     *
     * @param view The view to use to render the prompt target
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTargetRenderView(@Nullable final View view)
    {
        mTargetRenderView = view;
        return (T) this;
    }

    /**
     * Get the view that is rendered as the target.
     *
     * @return The view used to render the prompt target.
     */
    @Nullable
    public View getTargetRenderView()
    {
        return mTargetRenderView;
    }

    /**
     * Has the target been set successfully?
     *
     * @return True if set successfully.
     */
    public boolean isTargetSet()
    {
        return mTargetSet;
    }

    /**
     * Set the primary text using the given resource id.
     *
     * @param resId The string resource id for the primary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryText(@StringRes final int resId)
    {
        mPrimaryText = mResourceFinder.getString(resId);
        return (T) this;
    }

    /**
     * Set the primary text to the given string
     *
     * @param text The primary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryText(@Nullable final String text)
    {
        mPrimaryText = text;
        return (T) this;
    }

    /**
     * Set the primary text to the given CharSequence.
     * It is recommended that you don't go crazy with custom Spannables.
     *
     * @param text The primary text as CharSequence
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryText(@Nullable final CharSequence text)
    {
        mPrimaryText = text;
        return (T) this;
    }

    /**
     * Get the text to draw for the primary text.
     *
     * @return The primary text.
     */
    @Nullable
    public CharSequence getPrimaryText()
    {
        return mPrimaryText;
    }

    /**
     * Set the primary text font size.
     *
     * @param size The primary text font size
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryTextSize(@Dimension final float size)
    {
        mPrimaryTextSize = size;
        return (T) this;
    }

    /**
     * Set the primary text font size using the given resource id.
     *
     * @param resId The resource id for the primary text size
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryTextSize(@DimenRes final int resId)
    {
        mPrimaryTextSize = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Get the primary text font size.
     *
     * @return The primary text font size.
     */
    @Dimension
    public float getPrimaryTextSize()
    {
        return mPrimaryTextSize;
    }

    /**
     * Set the primary text colour.
     *
     * @param colour The primary text colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryTextColour(@ColorInt final int colour)
    {
        mPrimaryTextColour = colour;
        return (T) this;
    }

    /**
     * Gets the primary text font colour.
     *
     * @return The primary text font colour.
     */
    @ColorInt
    public int getPrimaryTextColour()
    {
        return mPrimaryTextColour;
    }

    /**
     * Sets the typeface and style used to display the primary text.
     *
     * @param typeface The primary text typeface
     */
    @NonNull
    public T setPrimaryTextTypeface(@Nullable final Typeface typeface)
    {
        return setPrimaryTextTypeface(typeface, 0);
    }

    /**
     * Sets the typeface used to display the primary text.
     *
     * @param typeface The primary text typeface
     * @param style    The typeface style
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryTextTypeface(@Nullable final Typeface typeface, final int style)
    {
        mPrimaryTextTypeface = typeface;
        mPrimaryTextTypefaceStyle = style;
        return (T) this;
    }

    /**
     * Get the typeface for the primary text.
     *
     * @return The primary text typeface.
     */
    @Nullable
    public Typeface getPrimaryTextTypeface()
    {
        return mPrimaryTextTypeface;
    }

    /**
     * Get the primary text typeface style.
     *
     * @return the primary text typeface style.
     */
    public int getPrimaryTextTypefaceStyle()
    {
        return mPrimaryTextTypefaceStyle;
    }

    /**
     * Set the secondary text using the given resource id.
     *
     * @param resId The secondary text resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryText(@StringRes final int resId)
    {
        mSecondaryText = mResourceFinder.getString(resId);
        return (T) this;
    }

    /**
     * Set the secondary text.
     *
     * @param text The secondary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryText(@Nullable final String text)
    {
        mSecondaryText = text;
        return (T) this;
    }

    /**
     * Set the secondary text.
     * It is recommended that you don't go crazy with custom Spannables.
     *
     * @param text The secondary text as a CharSequence
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryText(@Nullable final CharSequence text)
    {
        mSecondaryText = text;
        return (T) this;
    }

    /**
     * Get the secondary text.
     *
     * @return The secondary text.
     */
    @Nullable
    public CharSequence getSecondaryText()
    {
        return mSecondaryText;
    }

    /**
     * Set the secondary text font size using the give resource id.
     *
     * @param resId The secondary text string resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryTextSize(@DimenRes final int resId)
    {
        mSecondaryTextSize = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Set the secondary text font size.
     *
     * @param size The secondary text font size
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryTextSize(@Dimension final float size)
    {
        mSecondaryTextSize = size;
        return (T) this;
    }

    /**
     * Get the secondary text size.
     *
     * @return The secondary text size.
     */
    @Dimension
    public float getSecondaryTextSize()
    {
        return mSecondaryTextSize;
    }

    /**
     * Set the secondary text colour.
     *
     * @param colour The secondary text colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryTextColour(@ColorInt final int colour)
    {
        mSecondaryTextColour = colour;
        return (T) this;
    }

    /**
     * Get the secondary text colour.
     *
     * @return The secondary text colour.
     */
    public int getSecondaryTextColour()
    {
        return mSecondaryTextColour;
    }

    /**
     * Sets the typeface used to display the secondary text.
     *
     * @param typeface The secondary text typeface
     */
    @NonNull
    public T setSecondaryTextTypeface(@Nullable final Typeface typeface)
    {
        return setSecondaryTextTypeface(typeface, 0);
    }

    /**
     * Sets the typeface and style used to display the secondary text.
     *
     * @param typeface The secondary text typeface
     * @param style    The typeface style
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryTextTypeface(@Nullable final Typeface typeface, final int style)
    {
        mSecondaryTextTypeface = typeface;
        mSecondaryTextTypefaceStyle = style;
        return (T) this;
    }

    /**
     * Get the secondary text typeface.
     *
     * @return The secondary text typeface.
     */
    @Nullable
    public Typeface getSecondaryTextTypeface()
    {
        return mSecondaryTextTypeface;
    }

    /**
     * Get the secondary text typeface style.
     *
     * @return The secondary text typeface style.
     */
    public int getSecondaryTextTypefaceStyle()
    {
        return mSecondaryTextTypefaceStyle;
    }

    /**
     * Set the accessibility content description text using the given resource id.
     *
     * @param resId The string resource id for the accessibility content description text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setContentDescription(@StringRes final int resId)
    {
        mContentDescription = mResourceFinder.getString(resId);
        return (T) this;
    }

    /**
     * Set the accessibility content description text to the given string
     *
     * @param text The accessibility content description text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setContentDescription(@Nullable final String text)
    {
        mContentDescription = text;
        return (T) this;
    }

    /**
     * Get the text for the accessibility content description.
     * Defaults to a concatenation of primary and secondary texts.
     *
     * @return The accessibility content description text.
     */
    @Nullable
    public String getContentDescription()
    {
        if (mContentDescription != null)
        {
            return mContentDescription;
        }
        else
        {
            return String.format("%s. %s", mPrimaryText, mSecondaryText);
        }
    }

    /**
     * Set the text left and right padding using the given resource id.
     *
     * @param resId The text padding dimension resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTextPadding(@DimenRes final int resId)
    {
        mTextPadding = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Set the text left and right padding.
     *
     * @param padding The padding on the text left and right
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTextPadding(@Dimension final float padding)
    {
        mTextPadding = padding;
        return (T) this;
    }

    /**
     * Get the text left and right padding.
     *
     * @return The text left and right padding.
     */
    @Dimension
    public float getTextPadding()
    {
        return mTextPadding;
    }

    /**
     * Set the distance between the primary and secondary text using the given resource id.
     *
     * @param resId The dimension resource id for the text separation
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTextSeparation(@DimenRes final int resId)
    {
        mTextSeparation = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Set the distance between the primary and secondary text.
     *
     * @param separation The distance separation between the primary and secondary text
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTextSeparation(@Dimension final float separation)
    {
        mTextSeparation = separation;
        return (T) this;
    }

    /**
     * Get the distance between the primary and secondary text.
     *
     * @return the distance between the primary and secondary text.
     */
    @Dimension
    public float getTextSeparation()
    {
        return mTextSeparation;
    }

    /**
     * Set the padding between the text and the focal point using the given resource id.
     *
     * @param resId The dimension resource id for the focal to text distance
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setFocalPadding(@DimenRes final int resId)
    {
        mFocalPadding = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Set the padding between the text and the focal point.
     *
     * @param padding The distance between the text and focal
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setFocalPadding(@Dimension final float padding)
    {
        mFocalPadding = padding;
        return (T) this;
    }

    /**
     * Get the padding between the text and the focal.
     *
     * @return The padding between the text and the focal.
     */
    @Dimension
    public float getFocalPadding()
    {
        return mFocalPadding;
    }

    /**
     * Set the interpolator to use in animations.
     *
     * @param interpolator The animation interpolator to use
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setAnimationInterpolator(@Nullable final Interpolator interpolator)
    {
        mAnimationInterpolator = interpolator;
        return (T) this;
    }

    /**
     * Get the animation interpolator that is used.
     *
     * @return The animation interpolator that is used.
     */
    @Nullable
    public Interpolator getAnimationInterpolator()
    {
        return mAnimationInterpolator;
    }

    /**
     * Enable/disable focal animation.
     * true by default
     *
     * @param enabled Idle animation enabled
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIdleAnimationEnabled(final boolean enabled)
    {
        mIdleAnimationEnabled = enabled;
        return (T) this;
    }

    /**
     * Is the focal animation enabled.
     *
     * @return True if the idle animation is enabled.
     */
    public boolean getIdleAnimationEnabled()
    {
        return mIdleAnimationEnabled;
    }

    /**
     * Set the icon to draw in the focal point using the given resource id.
     *
     * @param resId The drawable resource id for the icon
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIcon(@DrawableRes final int resId)
    {
        mIconDrawable = mResourceFinder.getDrawable(resId);
        return (T) this;
    }

    /**
     * Set the icon to draw in the focal point.
     *
     * @param drawable The drawable for the icon
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIconDrawable(@Nullable final Drawable drawable)
    {
        mIconDrawable = drawable;
        return (T) this;
    }

    /**
     * Get the icon drawn as the target.
     *
     * @return The icon drawn as the target.
     */
    @Nullable
    public Drawable getIconDrawable()
    {
        return mIconDrawable;
    }

    /**
     * Applies a tint to the icon drawable
     *
     * @param tint the tint to apply to the icon drawable, {@code null} will remove the tint.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIconDrawableTintList(@Nullable ColorStateList tint)
    {
        mIconDrawableTintList = tint;
        mHasIconDrawableTint = tint != null;
        return (T) this;
    }

    /**
     * Sets the PorterDuff mode to use to apply the tint.
     *
     * @param tintMode the tint mode to use on the icon drawable, {@code null} will remove the
     *                 tint.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIconDrawableTintMode(@Nullable PorterDuff.Mode tintMode)
    {
        mIconDrawableTintMode = tintMode;
        if (tintMode == null)
        {
            mIconDrawableTintList = null;
            mHasIconDrawableTint = false;
        }
        return (T) this;
    }

    /**
     * Sets the colour to use to tint the icon drawable.
     *
     * @param colour The colour to use to tint the icon drawable, call {@link
     *               #setIconDrawableTintList(ColorStateList)} or {@link
     *               #setIconDrawableTintMode(PorterDuff.Mode)} with {@code null} to remove the
     *               tint.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIconDrawableColourFilter(@ColorInt final int colour)
    {
        mIconDrawableColourFilter = colour;
        mIconDrawableTintList = null;
        mHasIconDrawableTint = true;
        return (T) this;
    }

    /**
     * Set the listener to listen for when the prompt state changes.
     *
     * @param listener The listener to use
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPromptStateChangeListener(
            @Nullable final MaterialTapTargetPrompt.PromptStateChangeListener listener)
    {
        mPromptStateChangeListener = listener;
        return (T) this;
    }

    /**
     * Set the internal listener to listen for when the prompt state changes.
     * This does not return a builder is it's not intended to be user during the
     * creation of Prompts
     *
     * @param listener The listener to use
     */
    public void setSequenceListener(
            @Nullable final MaterialTapTargetPrompt.PromptStateChangeListener listener)
    {
        mSequencePromptStateChangeListener = listener;
    }


    /**
     * Handles emitting the prompt state changed events.
     *
     * @param state The state that the prompt is now in.
     */
    public void onPromptStateChanged(@NonNull final MaterialTapTargetPrompt prompt, final int state)
    {
        if (mPromptStateChangeListener != null)
        {
            mPromptStateChangeListener.onPromptStateChanged(prompt, state);
        }
    }


    /**
     * Handles emitting the additional prompt state changed events.
     *
     * @param state The state that the prompt is now in.
     */
    public void onExtraPromptStateChanged(@NonNull final MaterialTapTargetPrompt prompt, final int state)
    {
        if (mSequencePromptStateChangeListener != null)
        {
            mSequencePromptStateChangeListener.onPromptStateChanged(prompt, state);
        }
    }


    /**
     * Set if the prompt should stop touch events on the focal point from passing to underlying
     * views. Default is false.
     *
     * @param captureTouchEvent True to capture touch events in the prompt
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setCaptureTouchEventOnFocal(final boolean captureTouchEvent)
    {
        mCaptureTouchEventOnFocal = captureTouchEvent;
        return (T) this;
    }

    /**
     * Get if the prompt should stop touch events on the focal point from passing to underlying
     * views.
     *
     * @return True to capture touch events in the prompt
     */
    public boolean getCaptureTouchEventOnFocal()
    {
        return mCaptureTouchEventOnFocal;
    }

    /**
     * Set the max width that the primary and secondary text can be using the given resource
     * id.
     *
     * @param resId The dimension resource id for the max width that the text can reach
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setMaxTextWidth(@DimenRes final int resId)
    {
        mMaxTextWidth = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Set the max width that the primary and secondary text can be.
     *
     * @param width The max width that the text can reach
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setMaxTextWidth(@Dimension final float width)
    {
        mMaxTextWidth = width;
        return (T) this;
    }

    /**
     * Get the maximum width that the primary and secondary text can be.
     *
     * @return The maximum text width.
     */
    @Dimension
    public float getMaxTextWidth()
    {
        return mMaxTextWidth;
    }

    /**
     * Set the background colour.
     * The Material Design Guidelines specify that this should be 244 or hex F4.
     *
     * @param colour The background colour colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setBackgroundColour(@ColorInt final int colour)
    {
        mBackgroundColour = colour;
        return (T) this;
    }

    /**
     * Get the background colour.
     *
     * @return The background colour.
     */
    @ColorInt
    public int getBackgroundColour()
    {
        return mBackgroundColour;
    }

    /**
     * Set the focal point colour.
     *
     * @param colour The focal colour colour resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setFocalColour(@ColorInt final int colour)
    {
        mFocalColour = colour;
        return (T) this;
    }

    /**
     * Get the focal point colour.
     *
     * @return The focal point colour.
     */
    @ColorInt
    public int getFocalColour()
    {
        return mFocalColour;
    }

    /**
     * Set the focal point radius using the given resource id.
     *
     * @param resId The focal radius dimension resource id
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setFocalRadius(@DimenRes final int resId)
    {
        mFocalRadius = mResourceFinder.getResources().getDimension(resId);
        return (T) this;
    }

    /**
     * Set the focal point radius.
     *
     * @param radius The focal radius
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setFocalRadius(@Dimension final float radius)
    {
        mFocalRadius = radius;
        return (T) this;
    }

    /**
     * Get the focal point radius for the circle prompt focal.
     *
     * @return The radius used for the circle prompt focal.
     */
    @Dimension
    public float getFocalRadius()
    {
        return mFocalRadius;
    }

    /**
     * Set whether the prompt should dismiss itself when a touch event occurs outside the focal.
     * Default is true.
     *
     * Listen for the {@link MaterialTapTargetPrompt#STATE_NON_FOCAL_PRESSED} event in the
     * {@link #setPromptStateChangeListener(MaterialTapTargetPrompt.PromptStateChangeListener)} to handle the prompt
     * being pressed outside the focal area.
     *
     * @param autoDismiss True - prompt will dismiss when touched outside the focal, false - no
     *                    action taken.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setAutoDismiss(final boolean autoDismiss)
    {
        mAutoDismiss = autoDismiss;
        return (T) this;
    }

    /**
     * Get whether the prompt should dismiss itself when a touch event occurs outside the focal.
     *
     * @return True - prompt will dismiss when touched outside the focal, false - no
     *                    action taken.
     */
    public boolean getAutoDismiss()
    {
        return mAutoDismiss;
    }

    /**
     * Set whether the prompt should finish itself when a touch event occurs inside the focal.
     * Default is true.
     *
     * Listen for the {@link MaterialTapTargetPrompt#STATE_FOCAL_PRESSED} event in the
     * {@link #setPromptStateChangeListener(MaterialTapTargetPrompt.PromptStateChangeListener)} to handle the prompt
     * target being pressed.
     *
     * @param autoFinish True - prompt will finish when touched inside the focal, false - no
     *                   action taken.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setAutoFinish(final boolean autoFinish)
    {
        mAutoFinish = autoFinish;
        return (T) this;
    }

    /**
     * Get if the prompt should finish itself when a touch event occurs inside the focal.
     *
     * @return True if the prompt should finish itself when a touch event occurs inside the focal.
     */
    public boolean getAutoFinish()
    {
        return mAutoFinish;
    }

    /**
     * Set if the prompt should stop touch events outside the prompt from passing to underlying
     * views. Default is false.
     *
     * @param captureTouchEventOutsidePrompt True to capture touch events out side the prompt
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setCaptureTouchEventOutsidePrompt(
            final boolean captureTouchEventOutsidePrompt)
    {
        mCaptureTouchEventOutsidePrompt = captureTouchEventOutsidePrompt;
        return (T) this;
    }

    /**
     * Get if the prompt should stop touch events outside the prompt from passing to underlying views.
     *
     * @return True if touch events will not be passed to views below the prompt.
     */
    public boolean getCaptureTouchEventOutsidePrompt()
    {
        return mCaptureTouchEventOutsidePrompt;
    }

    /**
     * Set the primary and secondary text horizontal layout gravity.
     * Default: {@link Gravity#START}
     *
     * @param gravity The horizontal gravity
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setTextGravity(final int gravity)
    {
        mPrimaryTextGravity = gravity;
        mSecondaryTextGravity = gravity;
        return (T) this;
    }

    /**
     * Set the primary text horizontal layout gravity.
     * Default: {@link Gravity#START}
     *
     * @param gravity The horizontal gravity
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPrimaryTextGravity(final int gravity)
    {
        mPrimaryTextGravity = gravity;
        return (T) this;
    }

    /**
     * Gets the gravity for the primary text.
     *
     * @return The primary texts gravity.
     */
    public int getPrimaryTextGravity()
    {
        return mPrimaryTextGravity;
    }

    /**
     * Set the secondary text horizontal layout gravity.
     * Default: {@link Gravity#START}
     *
     * @param gravity The horizontal gravity
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setSecondaryTextGravity(final int gravity)
    {
        mSecondaryTextGravity = gravity;
        return (T) this;
    }

    /**
     * Gets the gravity for the secondary text.
     *
     * @return The secondary texts gravity.
     */
    public int getSecondaryTextGravity()
    {
        return mSecondaryTextGravity;
    }

    /**
     * Set the view to clip the prompt to.
     * The prompt won't draw outside the bounds of this view.
     * Default: {@link android.R.id#content}
     * <p>
     * Null can be used to stop the prompt being clipped to a view.
     *
     * @param view The view to clip to
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setClipToView(@Nullable final View view)
    {
        mClipToView = view;
        return (T) this;
    }

    /**
     * Get the view that the prompt canvas is clipped to.
     * The prompt won't draw outside the bounds of this view.
     *
     * @return The view that the prompt canvas is clipped to.
     */
    @Nullable
    public View getClipToView()
    {
        return mClipToView;
    }

    /**
     * Back button can be used to dismiss the prompt.
     * Default: true
     *
     * @param enabled True for back button dismiss enabled
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setBackButtonDismissEnabled(final boolean enabled)
    {
        mBackButtonDismissEnabled = enabled;
        return (T) this;
    }

    /**
     * Will the pressing the system back button dismiss the prompt.
     *
     * @return True if pressing the system back button will dismiss the prompt, false otherwise.
     */
    public boolean getBackButtonDismissEnabled()
    {
        return mBackButtonDismissEnabled;
    }

    /**
     * Indicates whether to ignore system status bar. Drawing area will be increased to the top of
     * screen regardless of status bar if this flag is true (status bar should be transparent to see
     * any effect from this)
     * Default: false
     * @param enabled true for drawing behind system status bar
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setIgnoreStatusBar(final boolean enabled)
    {
        mIgnoreStatusBar = enabled;
        return (T) this;
    }

    /**
     * Get ignore status bar flag
     * @return true if status bar should be ignored, otherwise false
     */
    public boolean getIgnoreStatusBar()
    {
        return mIgnoreStatusBar;
    }

    /**
     * Sets the renderer for the prompt background.
     *
     * @param promptBackground The background shape to use.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPromptBackground(@NonNull final PromptBackground promptBackground)
    {
        mPromptBackground = promptBackground;
        return (T) this;
    }

    /**
     * Get the prompt focal renderer.
     *
     * @return The prompt focal instance.
     */
    @NonNull
    public PromptBackground getPromptBackground()
    {
        return mPromptBackground;
    }

    /**
     * Sets the renderer for the prompt focal.
     *
     * @param promptFocal The focal shape to use.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPromptFocal(@NonNull final PromptFocal promptFocal)
    {
        mPromptFocal = promptFocal;
        return (T) this;
    }

    /**
     * Get the prompt focal renderer.
     *
     * @return The prompt focal instance.
     */
    @NonNull
    public PromptFocal getPromptFocal()
    {
        return mPromptFocal;
    }

    /**
     * Set the {@link PromptText} implementation to use to render the prompt text.
     *
     * @param promptText The prompt text implementation.
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public T setPromptText(@NonNull final PromptText promptText)
    {
        mPromptText = promptText;
        return (T) this;
    }

    /**
     * Get the {@link PromptText} implementation used to render the prompt text.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    @NonNull
    public PromptText getPromptText()
    {
        return mPromptText;
    }

    /**
     * Creates an {@link MaterialTapTargetPrompt} with the arguments supplied to this
     * builder.
     * <p>
     * Calling this method does not display the prompt. If no additional
     * processing is needed, {@link #show()} may be called instead to both
     * create and display the prompt.
     * </p>
     * <p>
     * Will return null if a valid target has not been set or the primary text is null.
     * To check that a valid target has been set call {@link #isTargetSet()}.
     * </p>
     *
     * @return The created builder or null if no target
     */
    @Nullable
    public MaterialTapTargetPrompt create()
    {
        if (!mTargetSet || (mPrimaryText == null && mSecondaryText == null))
        {
            return null;
        }
        final MaterialTapTargetPrompt mPrompt = MaterialTapTargetPrompt.createDefault(this);

        if (mAnimationInterpolator == null)
        {
            mAnimationInterpolator = new AccelerateDecelerateInterpolator();
        }

        if (mIconDrawable != null)
        {
            mIconDrawable.mutate();
            mIconDrawable.setBounds(0, 0, mIconDrawable.getIntrinsicWidth(), mIconDrawable.getIntrinsicHeight());
            if (mHasIconDrawableTint)
            {
                if (mIconDrawableTintList != null)
                {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                    {
                        mIconDrawable.setTintList(mIconDrawableTintList);
                    }
                }
                else
                {
                    mIconDrawable.setColorFilter(mIconDrawableColourFilter, mIconDrawableTintMode);
                    mIconDrawable.setAlpha(Color.alpha(mIconDrawableColourFilter));
                }
            }
        }

        mPromptBackground.setColour(getBackgroundColour());

        mPromptFocal.setColour(getFocalColour());
        mPromptFocal.setRippleAlpha(150);
        mPromptFocal.setDrawRipple(getIdleAnimationEnabled());
        if (mPromptFocal instanceof CirclePromptFocal)
        {
            ((CirclePromptFocal) mPromptFocal).setRadius(getFocalRadius());
        }

        return mPrompt;
    }

    /**
     * Creates a {@link MaterialTapTargetPrompt} with the arguments supplied to this
     * builder and immediately displays the prompt.
     * <p>
     * Calling this method is functionally identical to:
     * </p>
     * <pre>
     *     MaterialTapTargetPrompt prompt = builder.create();
     *     prompt.show();
     * </pre>
     * <p>
     * Will return null if a valid target has not been set or the primary text and secondary
     * text are null.
     * To check that a valid target has been set call {@link #isTargetSet()}.
     * </p>
     *
     * @return The created builder or null if no target
     */
    @Nullable
    public MaterialTapTargetPrompt show()
    {
        final MaterialTapTargetPrompt mPrompt = create();
        if (mPrompt != null)
        {
            mPrompt.show();
        }
        return mPrompt;
    }

    /**
     * Creates a {@link MaterialTapTargetPrompt} with the arguments supplied to this
     * builder and immediately displays the prompt for the number of milliseconds supplied.
     * <p>
     * Calling this method is functionally identical to:
     * </p>
     * <pre>
     *     MaterialTapTargetPrompt prompt = builder.create();
     *     prompt.showFor(milliseconds);
     * </pre>
     * <p>
     * Will return null if a valid target has not been set or the primary text and secondary
     * text are null.
     * To check that a valid target has been set call {@link #isTargetSet()}.
     * </p>
     *
     * @param milliseconds The number of milliseconds to show the prompt for.
     * @return The created builder or null if no target
     */
    @Nullable
    public MaterialTapTargetPrompt showFor(final long milliseconds)
    {
        final MaterialTapTargetPrompt mPrompt = create();
        if (mPrompt != null)
        {
            mPrompt.showFor(milliseconds);
        }
        return mPrompt;
    }
}