/*
* ******************************************************************************
* Copyright (c) 2013 Roman Nurik, 2013-2014 Gabriele Mariotti.
*
* 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 it.gmariotti.cardslib.library.view.listener;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.TextView;
import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.CardArrayAdapter;
/**
* It is based on Roman Nurik code.
* See this link for original code:
* https://code.google.com/p/romannurik-code/source/browse/#git%2Fmisc%2Fundobar
*
*
*/
public class UndoBarController {
private View mBarView;
private TextView mMessageView;
private ViewPropertyAnimator mBarAnimator;
private Handler mHideHandler = new Handler();
private UndoListener mUndoListener;
private UndoBarHideListener mUndoBarHideListener;
// State objects
private Parcelable mUndoToken;
private CharSequence mUndoMessage;
private UndoBarUIElements mUndoBarUIElements;
/**
* Interface to listen the undo controller actions
*/
public interface UndoListener {
/*
* Called when you undo the action
*/
void onUndo(Parcelable undoToken);
}
/**
* Interface to listen for when the Undo controller hides the Undo Bar,
* after it times out from being shown for the currently dismissed mUndoToken.
*/
public interface UndoBarHideListener {
/**
* Called when the UndoBar is hidden after being shown.
* @param undoOccurred true if the user pressed the undo button
* for the current mUndoToken.
*/
void onUndoBarHide(boolean undoOccurred);
}
public UndoBarController(View undoBarView, UndoListener undoListener) {
this (undoBarView,undoListener,null);
}
public UndoBarController(View undoBarView, UndoListener undoListener,UndoBarUIElements undoBarUIElements) {
mBarView = undoBarView;
mBarAnimator = mBarView.animate();
mUndoListener = undoListener;
if (undoBarUIElements==null)
undoBarUIElements = new DefaultUndoBarUIElements();
mUndoBarUIElements = undoBarUIElements;
mMessageView = (TextView) mBarView.findViewById(mUndoBarUIElements.getUndoBarMessageId());
mBarView.findViewById(mUndoBarUIElements.getUndoBarButtonId())
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
hideUndoBar(false);
mUndoListener.onUndo(mUndoToken);
// Remove the reference to the undo token, since the undo has occurred.
mUndoToken = null;
}
});
setupAnimation();
if (mUndoBarUIElements.isEnabledUndoBarSwipeAction() != UndoBarUIElements.SwipeDirectionEnabled.NONE){
setupSwipeActionOnUndoBar();
}
hideUndoBar(true);
}
public void showUndoBar(boolean immediate, CharSequence message,
Parcelable undoToken, UndoBarHideListener undoBarHideListener) {
// We're replacing the existing UndoBarHideListener, meaning that
// the original object removal was not undone. So, execute
// onUndoBarHide for the previous listener.
if (mUndoBarHideListener != null) {
mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
}
mUndoToken = undoToken;
mUndoMessage = message;
mUndoBarHideListener = undoBarHideListener;
mMessageView.setText(mUndoMessage);
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable,
mBarView.getResources().getInteger(R.integer.list_card_undobar_hide_delay));
mBarView.setVisibility(View.VISIBLE);
if (immediate) {
mBarView.setAlpha(1);
} else {
if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.ALPHA) {
mBarAnimator.cancel();
mBarAnimator
.alpha(1)
.setDuration(
mBarView.getResources()
.getInteger(android.R.integer.config_shortAnimTime))
.setListener(null);
} else if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.TOPBOTTOM){
mBarAnimator.cancel();
mBarAnimator
.alpha(1)
.translationY(0)
.setDuration(
mBarView.getResources()
.getInteger(android.R.integer.config_mediumAnimTime))
.setListener(null);
}
}
}
public void hideUndoBar(boolean immediate) {
mHideHandler.removeCallbacks(mHideRunnable);
if (immediate) {
mBarView.setVisibility(View.GONE);
mBarView.setAlpha(0);
mUndoMessage = null;
if (mUndoBarHideListener != null) {
// The undo has occurred only if mUndoToken was set to null.
mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
}
mUndoBarHideListener = null;
mUndoToken = null;
} else {
mBarAnimator.cancel();
if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.ALPHA) {
mBarAnimator
.alpha(0)
.setDuration(mBarView.getResources()
.getInteger(android.R.integer.config_shortAnimTime))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBarView.setVisibility(View.GONE);
mUndoMessage = null;
if (mUndoBarHideListener != null) {
// The undo has occurred only if mUndoToken was set to null.
mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
}
mUndoBarHideListener = null;
mUndoToken = null;
}
});
} else if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.TOPBOTTOM){
mBarAnimator
.alpha(0)
.translationY(+mBarView.getHeight())
.setDuration(mBarView.getResources()
.getInteger(android.R.integer.config_shortAnimTime))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBarView.setVisibility(View.GONE);
mUndoMessage = null;
if (mUndoBarHideListener != null) {
// The undo has occurred only if mUndoToken was set to null.
mUndoBarHideListener.onUndoBarHide(mUndoToken == null);
}
mUndoBarHideListener = null;
mUndoToken = null;
}
});
}
}
}
public void onSaveInstanceState(Bundle outState) {
outState.putCharSequence("undo_message", mUndoMessage);
outState.putParcelable("undo_token", mUndoToken);
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mUndoMessage = savedInstanceState.getCharSequence("undo_message");
mUndoToken = savedInstanceState.getParcelable("undo_token");
if (mUndoToken != null || !TextUtils.isEmpty(mUndoMessage)) {
showUndoBar(true, mUndoMessage, mUndoToken, mUndoBarHideListener);
}
}
}
private Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
hideUndoBar(false);
}
};
public Parcelable getUndoToken(){
return mUndoToken;
}
private void setupAnimation(){
if (mUndoBarUIElements.getAnimationType() == UndoBarUIElements.AnimationType.TOPBOTTOM) {
mBarView.setTranslationY(mBarView.getHeight());
}
}
private void setupSwipeActionOnUndoBar() {
if (mBarView != null) {
if (mUndoBarUIElements.isEnabledUndoBarSwipeAction() == UndoBarUIElements.SwipeDirectionEnabled.LEFTRIGHT) {
mBarView.setOnTouchListener(new SwipeDismissTouchListener(mBarView, null,
new SwipeDismissTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(Object token) {
return mUndoBarUIElements.isEnabledUndoBarSwipeAction() != UndoBarUIElements.SwipeDirectionEnabled.NONE;
}
@Override
public void onDismiss(View view, Object token) {
hideUndoBar(true);
mUndoListener.onUndo(mUndoToken);
// Remove the reference to the undo token, since the undo has occurred.
mUndoToken = null;
}
}));
} else if (mUndoBarUIElements.isEnabledUndoBarSwipeAction() == UndoBarUIElements.SwipeDirectionEnabled.TOPBOTTOM) {
mBarView.setOnTouchListener(new SwipeDismissTopBottomTouchListener(mBarView, null,
new SwipeDismissTopBottomTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(Object token) {
return mUndoBarUIElements.isEnabledUndoBarSwipeAction() != UndoBarUIElements.SwipeDirectionEnabled.NONE;
}
@Override
public void onDismiss(View view, Object token) {
hideUndoBar(true);
mUndoListener.onUndo(mUndoToken);
// Remove the reference to the undo token, since the undo has occurred.
mUndoToken = null;
}
}));
}
}
}
// -------------------------------------------------------------
// Undo Custom Bar
// -------------------------------------------------------------
/**
* Interface to set the ui elements in undo bar
*/
public interface UndoBarUIElements{
/**
* UndoBar id
* @return
*/
public int getUndoBarId();
/**
* TextView Id which displays message
*
* @return
*/
public int getUndoBarMessageId();
/**
* UndoButton Id
*
* @return
*/
public int getUndoBarButtonId();
/**
* UndoMessage.
* Implement this method to customize the undo message dynamically.
* </p>
* You can't find the cards with these positions in your arrayAdapter because the cards are removed.
* You have to/can use your id itemIds, to identify your cards.
*
* @param cardArrayAdapter array Adapter
* @param itemIds ids of items
* @param itemPositions position of removed items
* @return
*/
public String getMessageUndo(CardArrayAdapter cardArrayAdapter,String[] itemIds,int[] itemPositions);
/**
* Define the swipe action to remove the undobar.
* @return
*/
public SwipeDirectionEnabled isEnabledUndoBarSwipeAction();
/**
* Define the animation type for the undobar when it appears.
* @return
*/
public AnimationType getAnimationType();
/**
* Enum to define the animation type of the undobar.<p/>
* Use the {@link AnimationType#ALPHA} for an alpha animation, or {@link AnimationType#TOPBOTTOM} for a translation from bottom to top.
*/
public enum AnimationType {
ALPHA(0),
TOPBOTTOM(1);
private final int mValue;
private AnimationType(int value) {
mValue = value;
}
public int getValue() {
return mValue;
}
}
/**
* Enum to define the direction of the swipe action.
* <p/>
* You can use {@link SwipeDirectionEnabled#NONE} to disable the swipe action or {@link SwipeDirectionEnabled#LEFTRIGHT} to enable an action in left-right direction
* or {@link SwipeDirectionEnabled#TOPBOTTOM} to define a swipe action from top to bottom.
*/
public enum SwipeDirectionEnabled {
NONE(0),
LEFTRIGHT(1),
TOPBOTTOM(2);
private final int mValue;
private SwipeDirectionEnabled(int value) {
mValue = value;
}
public int getValue() {
return mValue;
}
}
}
/**
* Default UndoBar
*
* You can provide a custom UndoBar.
* This UndoBar has to contains these elements:
* <ul>
* <li>A TextView</li>
* <li>A Button</li>
* <li>A root element with an id attribute </li>
* </ul>
*
* You should use the same Ids provided in the default layout list_card_undo_message,
* but if you have to use different ids you can use the CardArrayAdapter.setUndoBarUIElements.
*
* Example:
* <code>
* mCardArrayAdapter.setUndoBarUIElements(new UndoBarController.DefaultUndoBarUIElements(){
*
* //Override methods to customize the elements
* }
* </code>
* It is very important to set the UndoBarUIElements before to call the setEnableUndo(true);
*
*/
public static class DefaultUndoBarUIElements implements UndoBarUIElements {
public DefaultUndoBarUIElements(){};
@Override
public int getUndoBarId() {
return R.id.list_card_undobar;
}
@Override
public int getUndoBarMessageId() {
return R.id.list_card_undobar_message;
}
@Override
public int getUndoBarButtonId() {
return R.id.list_card_undobar_button;
}
@Override
public String getMessageUndo(CardArrayAdapter cardArrayAdapter, String[] itemIds, int[] itemPositions) {
if (cardArrayAdapter!=null && cardArrayAdapter.getContext()!=null) {
Resources res = cardArrayAdapter.getContext().getResources();
if (res!=null)
return res.getQuantityString(R.plurals.list_card_undo_items, itemPositions.length, itemPositions.length);
}
return null;
}
@Override
public SwipeDirectionEnabled isEnabledUndoBarSwipeAction() {
return SwipeDirectionEnabled.NONE;
}
@Override
public AnimationType getAnimationType() {
return AnimationType.ALPHA;
}
};
/**
* Sets UndoBar UI Elements
*
* @return
*/
public UndoBarUIElements getUndoBarUIElements() {
return mUndoBarUIElements;
}
/**
* Returns UndoBar UI Elements
* @param undoBarUIElements
*/
public void setUndoBarUIElements(UndoBarUIElements undoBarUIElements) {
this.mUndoBarUIElements = undoBarUIElements;
}
}