package com.jensdriller.libs.undobar;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
@SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
public class UndoBar {
public enum Style {
/**
* The default style of the device's current API level.
*/
DEFAULT(R.layout.undo_bar),
/**
* The default style for API Level <= 18.
* <p/>
* Example:<br>
* <img src="https://camo.githubusercontent.com/3559ea695528c547ecdb918004b0c1df7ac83999/68747470733a2f2f7261772e6769746875622e636f6d2f6a656e7a7a2f416e64726f69642d556e646f4261722f6d61737465722f6173736574732f53637265656e73686f74312e706e67" />
* <br>
* <img src="https://camo.githubusercontent.com/22ac172d0a9e1273b87d9164a99c6a0933996164/68747470733a2f2f7261772e6769746875622e636f6d2f6a656e7a7a2f416e64726f69642d556e646f4261722f6d61737465722f6173736574732f53637265656e73686f74322e706e67" />
*/
HOLO(R.layout.undo_bar_holo),
/**
* The default style for API Level 19 + 20.
* <p/>
* Example:<br>
* <img src="https://camo.githubusercontent.com/bec5d8cf19564df3091cf5e2e77aff6760e88273/68747470733a2f2f7261772e6769746875622e636f6d2f6a656e7a7a2f416e64726f69642d556e646f4261722f6d61737465722f6173736574732f53637265656e73686f74332e706e67" />
* <br>
* <img src="https://camo.githubusercontent.com/107d8ed2fd880038b1d4a71dec9bbd1e02fd58e7/68747470733a2f2f7261772e6769746875622e636f6d2f6a656e7a7a2f416e64726f69642d556e646f4261722f6d61737465722f6173736574732f53637265656e73686f74342e706e67" />
*/
KITKAT(R.layout.undo_bar_kitkat),
/**
* The default style for API Level >= 21.
* <p/>
* Example:<br>
* <img src="https://camo.githubusercontent.com/a32255c0a1f5abe56607d46bb9782b8f338fd9e3/68747470733a2f2f7261772e6769746875622e636f6d2f6a656e7a7a2f416e64726f69642d556e646f4261722f6d61737465722f6173736574732f53637265656e73686f74352e706e67" />
* <br>
* <img src="https://camo.githubusercontent.com/62d186f3ce9d55fa2b114b62887c714733155d5e/68747470733a2f2f7261772e6769746875622e636f6d2f6a656e7a7a2f416e64726f69642d556e646f4261722f6d61737465722f6173736574732f53637265656e73686f74362e706e67" />
*/
LOLLIPOP(R.layout.undo_bar_lollipop);
private final int mLayoutResId;
private Style(int layoutResId) {
mLayoutResId = layoutResId;
}
int getLayoutResId() {
return mLayoutResId;
}
}
/**
* Listener for actions of the undo bar.
*/
public interface Listener {
/**
* Will be fired when the undo bar disappears without being actioned.
*/
void onHide();
/**
* Will be fired when the undo button is pressed.
*/
void onUndo(Parcelable token);
}
/**
* Default duration in milliseconds the undo bar will be displayed.
*/
public static final int DEFAULT_DURATION = 5000;
/**
* Default duration in milliseconds of the undo bar show and hide animation.
*/
public static final int DEFAULT_ANIMATION_DURATION = 300;
protected final Context mContext;
protected final UndoBarView mView;
protected final ViewCompat mViewCompat;
protected final Handler mHandler = new Handler();
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
onHide();
}
};
@SuppressWarnings("FieldCanBeLocal")
private final OnClickListener mOnUndoClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
onUndo();
}
};
protected Listener mUndoListener;
protected Parcelable mUndoToken;
protected CharSequence mUndoMessage;
protected int mDuration = DEFAULT_DURATION;
protected int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
protected boolean mUseEnglishLocale;
protected Style mStyle = Style.DEFAULT;
protected int mUndoColor = Color.WHITE;
protected boolean mAlignParentBottom;
/**
* Creates a new undo bar instance to be displayed in the given {@link Activity}.
*/
public UndoBar(Activity activity) {
this(activity.getWindow());
}
/**
* Creates a new undo bar instance to be displayed in the given {@link Activity}.
* <p/>
* The style forces the the undo bar to match the look and feel of a certain API level.<br>
* By default, it uses the style of the device's current API level.
* <p/>
* This is useful, for example, if you want to show a consistent
* Lollipop style across all API levels.
*/
public UndoBar(Activity activity, Style style) {
this(activity.getWindow(), style);
}
/**
* Creates a new undo bar instance to be displayed in the given {@link Dialog}.
*/
public UndoBar(Dialog dialog) {
this(dialog.getWindow());
}
/**
* Creates a new undo bar instance to be displayed in the given {@link Dialog}.
* <p/>
* The style forces the the undo bar to match the look and feel of a certain API level.<br>
* By default, it uses the style of the device's current API level.
* <p/>
* This is useful, for example, if you want to show a consistent
* Lollipop style across all API levels.
*/
public UndoBar(Dialog dialog, Style style) {
this(dialog.getWindow(), style);
}
/**
* Creates a new undo bar instance to be displayed in the given {@link Window}.
*/
public UndoBar(Window window) {
this(window, null);
}
/**
* Creates a new undo bar instance to be displayed in the given {@link Window}.
* <p/>
* The style forces the the undo bar to match the look and feel of a certain API level.<br>
* By default, it uses the style of the device's current API level.
* <p/>
* This is useful, for example, if you want to show a consistent
* Lollipop style across all API levels.
*/
public UndoBar(Window window, Style style) {
if (style == null) {
style = Style.DEFAULT;
}
mContext = window.getContext();
mStyle = style;
mView = getView(window);
mView.setOnUndoClickListener(mOnUndoClickListener);
mViewCompat = new ViewCompatImpl(mView);
hide(false);
}
/**
* Sets the message to be displayed on the left of the undo bar.
*/
public void setMessage(CharSequence message) {
mUndoMessage = message;
}
/**
* Sets the message to be displayed on the left of the undo bar.
*/
public void setMessage(int messageResId) {
mUndoMessage = mContext.getString(messageResId);
}
/**
* Sets the {@link Listener UndoBar.Listener}.
*/
public void setListener(Listener undoListener) {
mUndoListener = undoListener;
}
/**
* Sets a {@link Parcelable} token to the undo bar which will be returned in
* the {@link Listener UndoBar.Listener}.
*/
public void setUndoToken(Parcelable undoToken) {
mUndoToken = undoToken;
}
/**
* Sets the duration the undo bar will be shown.<br>
* Default is {@link #DEFAULT_DURATION}.
*
* @param duration in milliseconds
*/
public void setDuration(int duration) {
mDuration = duration;
}
/**
* Sets the duration of the animation for showing and hiding the undo bar.<br>
* Default is {@link #DEFAULT_ANIMATION_DURATION}.
*
* @param animationDuration in milliseconds
*/
public void setAnimationDuration(int animationDuration) {
mAnimationDuration = animationDuration;
}
/**
* Forces the English {@link java.util.Locale Locale} to be used explicitly.<br>
* This means that the undo bar label will always show <b>UNDO</b>
* regardless of the device's current {@link java.util.Locale Locale}.
*/
public void setUseEnglishLocale(boolean useEnglishLocale) {
mUseEnglishLocale = useEnglishLocale;
}
/**
* Sets the text color of the undo button.<br>
* The default color is white.<br>
* <b>Note:</b> This is only applied to the {@link UndoBar.Style#LOLLIPOP}
* style and ignored otherwise.
*/
public void setUndoColor(int color) {
mUndoColor = color;
}
/**
* Sets the text color resource id of the undo button.<br>
* The default color is white.<br>
* <b>Note:</b> This is only applied to the {@link UndoBar.Style#LOLLIPOP}
* style and ignored otherwise.
*/
public void setUndoColorResId(int colorResId) {
mUndoColor = mContext.getResources().getColor(colorResId);
}
/**
* If set to {@code true}, the undo bar will appear stuck at the bottom without any margins.<br>
* The default is {@code false}.<br>
* <b>Note:</b> This is only applied to the {@link UndoBar.Style#LOLLIPOP}
* style on devices with a smallest width of less than 600dp and ignored otherwise.
*/
public void setAlignParentBottom(boolean alignParentBottom) {
mAlignParentBottom = alignParentBottom;
}
/**
* Calls {@link #show(boolean)} with {@code shouldAnimate = true}.
*/
public void show() {
show(true);
}
/**
* Shows the {@link UndoBar}.
*
* @param shouldAnimate whether the {@link UndoBar} should animate in
*/
public void show(boolean shouldAnimate) {
mView.setMessage(mUndoMessage);
mView.setButtonLabel(mUseEnglishLocale ? R.string.undo_english : R.string.undo);
if (isLollipopStyle(mStyle)) {
mView.setUndoColor(mUndoColor);
if (mAlignParentBottom && isAlignBottomPossible()) {
removeMargins(mView);
}
}
mHandler.removeCallbacks(mHideRunnable);
mHandler.postDelayed(mHideRunnable, mDuration);
mView.setVisibility(View.VISIBLE);
if (shouldAnimate) {
animateIn();
} else {
mViewCompat.setAlpha(1);
}
}
/**
* Checks whether the given style is {@link Style#LOLLIPOP}.
* Either explicitly set or the system default.
*/
private boolean isLollipopStyle(Style style) {
return style == Style.LOLLIPOP || (style == Style.DEFAULT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}
/**
* Checks whether aligning the undo bar at the bottom is possible
* for the current device configuration.
*/
private boolean isAlignBottomPossible() {
return mContext.getResources().getBoolean(R.bool.is_align_bottom_possible);
}
/**
* Removes any margins from the given view.
*/
private static void removeMargins(View view) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = layoutParams.topMargin = layoutParams.rightMargin = layoutParams.bottomMargin = 0;
view.setLayoutParams(layoutParams);
}
/**
* Calls {@link #hide(boolean)} with {@code shouldAnimate = true}.
*/
public void hide() {
hide(true);
}
/**
* Hides the {@link UndoBar}.
*
* @param shouldAnimate whether the {@link UndoBar} should animate out
*/
public void hide(boolean shouldAnimate) {
mHandler.removeCallbacks(mHideRunnable);
if (shouldAnimate) {
animateOut();
} else {
mViewCompat.setAlpha(0);
mView.setVisibility(View.GONE);
mUndoMessage = null;
mUndoToken = null;
}
}
/**
* Checks if the undo bar is currently visible.
*
* @return {@code true} if visible, {@code false} otherwise
*/
public boolean isVisible() {
return mView.getVisibility() == View.VISIBLE;
}
/**
* Performs the actual show animation.
*/
protected void animateIn() {
mViewCompat.animateIn(mAnimationDuration);
}
/**
* Performs the actual hide animation.
*/
protected void animateOut() {
mViewCompat.animateOut(mAnimationDuration, new ViewCompat.AnimatorListener() {
@Override
public void onAnimationEnd() {
mView.setVisibility(View.GONE);
mUndoMessage = null;
mUndoToken = null;
}
});
}
/**
* Called when the undo bar disappears without being actioned.<br>
* Hides the undo bar and notifies potential listener.
*/
protected void onHide() {
hide(true);
safelyNotifyOnHide();
mUndoListener = null;
}
/**
* Called when the undo button is pressed.<br>
* Hides the undo bar and notifies potential listener.
*/
protected void onUndo() {
hide(true);
safelyNotifyOnUndo();
}
/**
* Notifies listener if available.
*/
protected void safelyNotifyOnHide() {
if (mUndoListener != null) {
mUndoListener.onHide();
}
}
/**
* Notifies listener if available.
*/
protected void safelyNotifyOnUndo() {
if (mUndoListener != null) {
mUndoListener.onUndo(mUndoToken);
}
}
/**
* Checks if there is already an {@link UndoBarView} instance added to the
* given {@link Window}.<br>
* If {@code true}, returns that instance.<br>
* If {@code false}, inflates a new {@link UndoBarView} and returns it.
*/
protected UndoBarView getView(Window window) {
ViewGroup decorView = (ViewGroup) window.getDecorView();
// if we're operating within an Activity, limit ourselves to the content view.
ViewGroup rootView = (ViewGroup) decorView.findViewById(android.R.id.content);
if (rootView == null) {
rootView = decorView;
}
// if it's the first undo bar in this window or a different style, inflate a new instance
UndoBarView undoBarView = (UndoBarView) rootView.findViewById(R.id.undoBar);
if (undoBarView == null || undoBarView.getTag() != mStyle) {
rootView.removeView(undoBarView); // remove potential undo bar w/ different style
undoBarView = (UndoBarView) LayoutInflater.from(rootView.getContext())
.inflate(mStyle.getLayoutResId(), rootView, false);
undoBarView.setTag(mStyle);
rootView.addView(undoBarView);
}
return undoBarView;
}
public static class Builder {
private final Window mWindow;
private CharSequence mUndoMessage;
private Listener mUndoListener;
private Parcelable mUndoToken;
private int mDuration = DEFAULT_DURATION;
private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
private boolean mUseEnglishLocale;
private Style mStyle;
private int mUndoColor = Color.WHITE;
private boolean mAlignParentBottom;
/**
* Constructor using the {@link android.app.Activity} in which the undo bar will be
* displayed
*/
public Builder(Activity activity) {
mWindow = activity.getWindow();
}
/**
* Constructor using the {@link android.app.Dialog} in which the undo bar will be
* displayed
*/
public Builder(Dialog dialog) {
mWindow = dialog.getWindow();
}
/**
* Constructor using the {@link Window} in which the undo bar will be
* displayed
*/
public Builder(Window window) {
mWindow = window;
}
/**
* Sets the message to be displayed on the left of the undo bar.
*/
public Builder setMessage(int messageResId) {
mUndoMessage = mWindow.getContext().getString(messageResId);
return this;
}
/**
* Sets the message to be displayed on the left of the undo bar.
*/
public Builder setMessage(CharSequence message) {
mUndoMessage = message;
return this;
}
/**
* Sets the {@link Listener UndoBar.Listener}.
*/
public Builder setListener(Listener undoListener) {
mUndoListener = undoListener;
return this;
}
/**
* Sets a {@link Parcelable} token to the undo bar which will be
* returned in the {@link Listener UndoBar.Listener}.
*/
public Builder setUndoToken(Parcelable undoToken) {
mUndoToken = undoToken;
return this;
}
/**
* Sets the duration the undo bar will be shown.<br>
* Default is {@link #DEFAULT_DURATION}.
*
* @param duration in milliseconds
*/
public Builder setDuration(int duration) {
mDuration = duration;
return this;
}
/**
* Sets the duration of the animation for showing and hiding the undo
* bar.<br>
* Default is {@link #DEFAULT_ANIMATION_DURATION}.
*
* @param animationDuration in milliseconds
*/
public Builder setAnimationDuration(int animationDuration) {
mAnimationDuration = animationDuration;
return this;
}
/**
* Forces the English {@link java.util.Locale Locale} to be used explicitly.<br>
* This means that the undo bar label will always show <b>UNDO</b>
* regardless of the device's current {@link java.util.Locale Locale}.
*/
public Builder setUseEnglishLocale(boolean useEnglishLocale) {
mUseEnglishLocale = useEnglishLocale;
return this;
}
/**
* Forces the style of the undo bar to match a certain API level.<br>
* By default, it uses the style of the device's current API level.
* <p/>
* This is useful, for example, if you want to show a consistent
* Lollipop style across all API levels.
*/
public Builder setStyle(Style style) {
mStyle = style;
return this;
}
/**
* Sets the text color of the undo button.<br>
* The default color is white.<br>
* <b>Note:</b> This is only applied to the {@link UndoBar.Style#LOLLIPOP}
* style and ignored otherwise.
*/
public Builder setUndoColor(int undoColor) {
mUndoColor = undoColor;
return this;
}
/**
* Sets the text color resource id of the undo button.<br>
* The default color is white.<br>
* <b>Note:</b> This is only applied to the {@link UndoBar.Style#LOLLIPOP}
* style and ignored otherwise.
*/
public Builder setUndoColorResId(int undoColorResId) {
mUndoColor = mWindow.getContext().getResources().getColor(undoColorResId);
return this;
}
/**
* If set to {@code true}, the undo bar will appear stuck at the bottom without any margins.<br>
* The default is {@code false}.<br>
* <b>Note:</b> This is only applied to the {@link UndoBar.Style#LOLLIPOP}
* style on devices with a smallest width of less than 600dp and ignored otherwise.
*/
public Builder setAlignParentBottom(boolean alignParentBottom) {
mAlignParentBottom = alignParentBottom;
return this;
}
/**
* Creates an {@link UndoBar} instance with this Builder's
* configuration.
*/
public UndoBar create() {
UndoBar undoBarController = new UndoBar(mWindow, mStyle);
undoBarController.setListener(mUndoListener);
undoBarController.setUndoToken(mUndoToken);
undoBarController.setMessage(mUndoMessage);
undoBarController.setDuration(mDuration);
undoBarController.setAnimationDuration(mAnimationDuration);
undoBarController.setUseEnglishLocale(mUseEnglishLocale);
undoBarController.setUndoColor(mUndoColor);
undoBarController.setAlignParentBottom(mAlignParentBottom);
return undoBarController;
}
/**
* Calls {@link #show(boolean)} with {@code shouldAnimate = true}.
*/
public void show() {
show(true);
}
/**
* Shows the {@link UndoBar} with this Builder's configuration.
*
* @param shouldAnimate whether the {@link UndoBar} should animate in and out.
*/
@SuppressWarnings("SameParameterValue")
public void show(boolean shouldAnimate) {
create().show(shouldAnimate);
}
}
}