diff -r 000000000000 -r 6474c204b198 mobile/android/base/widget/ButtonToast.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/widget/ButtonToast.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,181 @@ +/* + * Copyright 2012 Roman Nurik + * + * 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 org.mozilla.gecko.widget; + +import java.util.LinkedList; + +import org.mozilla.gecko.R; +import org.mozilla.gecko.animation.PropertyAnimator; + +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +public class ButtonToast { + private final static String LOGTAG = "GeckoButtonToast"; + private final static int TOAST_DURATION = 5000; + + private final View mView; + private final TextView mMessageView; + private final Button mButton; + private final Handler mHideHandler = new Handler(); + + private final ToastListener mListener; + private final LinkedList mQueue = new LinkedList(); + private Toast mCurrentToast; + + public enum ReasonHidden { + CLICKED, + TIMEOUT, + STARTUP + } + + // State objects + private static class Toast { + public final CharSequence buttonMessage; + public Drawable buttonDrawable; + public final CharSequence message; + public ToastListener listener; + + public Toast(CharSequence aMessage, CharSequence aButtonMessage, + Drawable aDrawable, ToastListener aListener) { + message = aMessage; + buttonMessage = aButtonMessage; + buttonDrawable = aDrawable; + listener = aListener; + } + } + + public interface ToastListener { + void onButtonClicked(); + void onToastHidden(ReasonHidden reason); + } + + public ButtonToast(View view) { + mView = view; + mListener = null; + mMessageView = (TextView) mView.findViewById(R.id.toast_message); + mButton = (Button) mView.findViewById(R.id.toast_button); + mButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Toast t = mCurrentToast; + if (t == null) + return; + + hide(false, ReasonHidden.CLICKED); + if (t.listener != null) { + t.listener.onButtonClicked(); + } + } + }); + + hide(true, ReasonHidden.STARTUP); + } + + public void show(boolean immediate, CharSequence message, + CharSequence buttonMessage, Drawable buttonDrawable, + ToastListener listener) { + show(new Toast(message, buttonMessage, buttonDrawable, listener), immediate); + } + + private void show(Toast t, boolean immediate) { + // If we're already showing a toast, add this one to the queue to show later + if (mView.getVisibility() == View.VISIBLE) { + mQueue.offer(t); + return; + } + + mCurrentToast = t; + mButton.setEnabled(true); + + mMessageView.setText(t.message); + mButton.setText(t.buttonMessage); + mButton.setCompoundDrawablePadding(mView.getContext().getResources().getDimensionPixelSize(R.dimen.toast_button_padding)); + mButton.setCompoundDrawablesWithIntrinsicBounds(t.buttonDrawable, null, null, null); + + mHideHandler.removeCallbacks(mHideRunnable); + mHideHandler.postDelayed(mHideRunnable, TOAST_DURATION); + + mView.setVisibility(View.VISIBLE); + int duration = immediate ? 0 : mView.getResources().getInteger(android.R.integer.config_longAnimTime); + + PropertyAnimator animator = new PropertyAnimator(duration); + animator.attach(mView, PropertyAnimator.Property.ALPHA, 1.0f); + animator.start(); + } + + public void hide(boolean immediate, ReasonHidden reason) { + if (mButton.isPressed() && reason != ReasonHidden.CLICKED) { + mHideHandler.postDelayed(mHideRunnable, TOAST_DURATION); + return; + } + + if (mCurrentToast != null && mCurrentToast.listener != null) { + mCurrentToast.listener.onToastHidden(reason); + } + mCurrentToast = null; + mButton.setEnabled(false); + mHideHandler.removeCallbacks(mHideRunnable); + int duration = immediate ? 0 : mView.getResources().getInteger(android.R.integer.config_longAnimTime); + + mView.clearAnimation(); + if (immediate) { + mView.setVisibility(View.GONE); + showNextInQueue(); + } else { + // Using Android's animation frameworks will not correctly turn off clicking. + // See bug 885717. + PropertyAnimator animator = new PropertyAnimator(duration); + animator.attach(mView, PropertyAnimator.Property.ALPHA, 0.0f); + animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener () { + // If we are showing a toast and go in the background + // onAnimationEnd will be called when the app is restored + public void onPropertyAnimationEnd() { + mView.setVisibility(View.GONE); + showNextInQueue(); + } + public void onPropertyAnimationStart() { } + }); + animator.start(); + } + } + + public void onSaveInstanceState(Bundle outState) { + // Add whatever toast we're currently showing to the front of the queue + if (mCurrentToast != null) { + mQueue.add(0, mCurrentToast); + } + } + + private void showNextInQueue() { + Toast t = mQueue.poll(); + if (t != null) { + show(t, false); + } + } + + private Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hide(false, ReasonHidden.TIMEOUT); + } + }; +}