blob: 1b0d8690411a35207ba16d486e4fd9adb2251b11 [file] [log] [blame]
/**
* Copyright (c) 2011, Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mail.ui;
import com.android.mail.R;
import com.android.mail.providers.Account;
import com.android.mail.providers.UIProvider;
import com.google.common.collect.ImmutableSet;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorInflater;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.text.Html;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
/**
* A custom {@link View} that exposes an {@link UndoOperation} to the user.
*/
public class UndoBarView extends FrameLayout {
/** The clickable view that has the 9-patch to handle focus */
private View mUndoButtonView;
private long mStartShowTime = -1;
private static final long MIN_SHOW_TIME = 1500;
public static final ImmutableSet<Integer> EXCLUDE_UNDO_OPS = ImmutableSet.of(R.id.unread,
R.id.star);
/** The view that contains the description of the action that just happened */
private TextView mUndoDescriptionView;
private OnUndoCancelListener mOnCancelListener;
private final Runnable mDelayedHide = new Runnable() {
@Override
public void run() {
internalHide(true);
}
};
private Handler mHandler;
private boolean mHidden = false;
private Animator mUndoShowAnimation;
private Animator mUndoHideAnimation;
private AnimatorListener mUndoShowListener;
private AnimatorListener mUndoHideListener;
public UndoBarView(Context context) {
this(context, null);
}
public UndoBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public UndoBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mHandler = new Handler();
}
public void setOnCancelListener(UndoBarView.OnUndoCancelListener listener) {
mOnCancelListener = listener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mUndoButtonView = findViewById(R.id.undo_button);
mUndoDescriptionView = (TextView) findViewById(R.id.undo_descriptionview);
}
/**
* Displays this view and makes it visible, binding the behavior to the
* specified {@link UndoOperation}.
*/
public void show(boolean animate, final Context context, String description,
final Account account) {
mUndoButtonView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View widget) {
if (account.undoUri != null) {
// NOTE: We might want undo to return the messages affected, in which case
// the resulting cursor might be interesting...
// TODO: Use UIProvider.SEQUENCE_QUERY_PARAMETER to indicate the set of
// commands to undo
Cursor c = context.getContentResolver().query(Uri.parse(account.undoUri),
UIProvider.UNDO_PROJECTION, null, null, null);
if (c != null) {
c.close();
}
}
setVisibility(View.GONE);
}
});
mUndoDescriptionView.setText(Html.fromHtml(description));
mUndoDescriptionView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View widget) {
setVisibility(View.GONE);
}
});
setVisibility(View.VISIBLE);
}
/**
* Hides the undo view and resets the state.
*/
public void doHide() {
if (!mHidden) {
long now = SystemClock.uptimeMillis();
long diff = now - mStartShowTime;
if (diff >= MIN_SHOW_TIME) {
internalHide(true);
} else {
mHandler.postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
}
mHidden = true;
}
}
/**
* Hides the undo view and resets the state.
*/
public void hide(boolean animate) {
if (!mHidden) {
mHidden = true;
internalHide(animate);
}
}
private void internalHide(boolean animate) {
if (isShown()) {
mUndoDescriptionView.setText("");
mUndoButtonView.setOnClickListener(null);
// Hide undo text once it's clicked.
if (animate) {
getUndoHideAnimation().start();
} else {
setVisibility(View.GONE);
onCancel();
}
}
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
}
private void onCancel() {
if (mOnCancelListener != null) {
mOnCancelListener.onUndoCancel();
}
}
public void setUndoShowListener(Animator.AnimatorListener listener) {
mUndoShowListener = listener;
}
public void setUndoHideListener(Animator.AnimatorListener listener) {
mUndoHideListener = listener;
}
private Animator getUndoShowAnimation() {
if (mUndoShowAnimation == null) {
mUndoShowAnimation = AnimatorInflater.loadAnimator(getContext(),
R.animator.fade_in);
mUndoShowAnimation.setTarget(this);
mUndoShowAnimation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationCancel(Animator animation) {
if (mUndoShowListener != null) {
mUndoShowListener.onAnimationCancel(animation);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (mUndoShowListener != null) {
mUndoShowListener.onAnimationEnd(animation);
}
}
@Override
public void onAnimationRepeat(Animator animation) {
if (mUndoShowListener != null) {
mUndoShowListener.onAnimationRepeat(animation);
}
}
@Override
public void onAnimationStart(Animator animation) {
setVisibility(View.VISIBLE);
if (mUndoShowListener != null) {
mUndoShowListener.onAnimationStart(animation);
}
}
});
}
return mUndoShowAnimation;
}
private Animator getUndoHideAnimation() {
if (mUndoHideAnimation == null) {
mUndoHideAnimation = AnimatorInflater.loadAnimator(getContext(),
R.animator.fade_out);
mUndoHideAnimation.setTarget(this);
mUndoHideAnimation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationCancel(Animator animation) {
if (mUndoHideListener != null) {
mUndoHideListener.onAnimationCancel(animation);
}
}
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.GONE);
if (mUndoHideListener != null) {
mUndoHideListener.onAnimationEnd(animation);
}
}
@Override
public void onAnimationRepeat(Animator animation) {
if (mUndoHideListener != null) {
mUndoHideListener.onAnimationRepeat(animation);
}
}
@Override
public void onAnimationStart(Animator animation) {
if (mUndoHideListener != null) {
mUndoHideListener.onAnimationStart(animation);
}
}
});
}
return mUndoHideAnimation;
}
/**
* Implemented by objects that need to know when the undo bar is cancelled.
*/
public interface OnUndoCancelListener {
public void onUndoCancel();
}
public boolean isEventInUndo(MotionEvent event) {
if (!isShown()) {
return false;
}
int[] xy = new int[2];
float x = event.getX();
float y = event.getY();
getLocationOnScreen(xy);
return (x > xy[0] && x < (xy[0] + getWidth()) && y > xy[1] && y < xy[1] + getHeight());
}
/**
* Classes that can undo an operation should implement this interface.
*/
public interface UndoHandler {
public void performUndo(UndoOperation undoOp);
}
}