mobile/android/base/home/HomeBanner.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.home;
     8 import org.json.JSONException;
     9 import org.json.JSONObject;
    10 import org.mozilla.gecko.GeckoAppShell;
    11 import org.mozilla.gecko.GeckoEvent;
    12 import org.mozilla.gecko.R;
    13 import org.mozilla.gecko.animation.PropertyAnimator;
    14 import org.mozilla.gecko.animation.PropertyAnimator.Property;
    15 import org.mozilla.gecko.animation.ViewHelper;
    16 import org.mozilla.gecko.gfx.BitmapUtils;
    17 import org.mozilla.gecko.util.GeckoEventListener;
    18 import org.mozilla.gecko.util.ThreadUtils;
    19 import org.mozilla.gecko.widget.EllipsisTextView;
    21 import android.content.Context;
    22 import android.graphics.drawable.Drawable;
    23 import android.os.Build;
    24 import android.text.Html;
    25 import android.text.Spanned;
    26 import android.text.TextUtils;
    27 import android.util.AttributeSet;
    28 import android.util.Log;
    29 import android.view.LayoutInflater;
    30 import android.view.MotionEvent;
    31 import android.view.View;
    32 import android.widget.ImageButton;
    33 import android.widget.ImageView;
    34 import android.widget.LinearLayout;
    35 import android.widget.TextView;
    37 public class HomeBanner extends LinearLayout
    38                         implements GeckoEventListener {
    39     private static final String LOGTAG = "GeckoHomeBanner";
    41     // Used for tracking scroll length
    42     private float mTouchY = -1;
    44     // Used to detect for upwards scroll to push banner all the way up
    45     private boolean mSnapBannerToTop;
    47     // Tracks whether or not the banner should be shown on the current panel.
    48     private boolean mActive = false;
    50     // The user is currently swiping between HomePager pages
    51     private boolean mScrollingPages = false;
    53     // Tracks whether the user swiped the banner down, preventing us from autoshowing when the user
    54     // switches back to the default page.
    55     private boolean mUserSwipedDown = false;
    57     // We must use this custom TextView to address an issue on 2.3 and lower where ellipsized text
    58     // will not wrap more than 2 lines.
    59     private final EllipsisTextView mTextView;
    60     private final ImageView mIconView;
    62     // The height of the banner view.
    63     private final float mHeight;
    65     // Listener that gets called when the banner is dismissed from the close button.
    66     private OnDismissListener mOnDismissListener;
    68     public interface OnDismissListener {
    69         public void onDismiss();
    70     }
    72     public HomeBanner(Context context) {
    73         this(context, null);
    74     }
    76     public HomeBanner(Context context, AttributeSet attrs) {
    77         super(context, attrs);
    79         LayoutInflater.from(context).inflate(R.layout.home_banner_content, this);
    81         mTextView = (EllipsisTextView) findViewById(R.id.text);
    82         mIconView = (ImageView) findViewById(R.id.icon);
    84         mHeight = getResources().getDimensionPixelSize(R.dimen.home_banner_height);
    86         // Disable the banner until a message is set.
    87         setEnabled(false);
    88     }
    90     @Override
    91     public void onAttachedToWindow() {
    92         super.onAttachedToWindow();
    94         // Tapping on the close button will ensure that the banner is never
    95         // showed again on this session.
    96         final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
    98         // The drawable should have 50% opacity.
    99         closeButton.getDrawable().setAlpha(127);
   101         closeButton.setOnClickListener(new View.OnClickListener() {
   102             @Override
   103             public void onClick(View view) {
   104                 HomeBanner.this.dismiss();
   106                 // Send the current message id back to JS.
   107                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Dismiss", (String) getTag()));
   108             }
   109         });
   111         setOnClickListener(new View.OnClickListener() {
   112             @Override
   113             public void onClick(View v) {
   114                 HomeBanner.this.dismiss();
   116                 // Send the current message id back to JS.
   117                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
   118             }
   119         });
   121         GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
   122     }
   124     @Override
   125     public void onDetachedFromWindow() {
   126         super.onDetachedFromWindow();
   128         GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
   129     }
   131     @Override
   132     public void setVisibility(int visibility) {
   133         // On pre-Honeycomb devices, setting the visibility to GONE won't actually
   134         // hide the view unless we clear animations first.
   135         if (Build.VERSION.SDK_INT < 11 && visibility == View.GONE) {
   136             clearAnimation();
   137         }
   139         super.setVisibility(visibility);
   140     }
   142     public void setScrollingPages(boolean scrollingPages) {
   143         mScrollingPages = scrollingPages;
   144     }
   146     public void setOnDismissListener(OnDismissListener listener) {
   147         mOnDismissListener = listener;
   148     }
   150     /**
   151      * Hides and disables the banner.
   152      */
   153     private void dismiss() {
   154         setVisibility(View.GONE);
   155         setEnabled(false);
   157         if (mOnDismissListener != null) {
   158             mOnDismissListener.onDismiss();
   159         }
   160     }
   162     /**
   163      * Sends a message to gecko to request a new banner message. UI is updated in handleMessage.
   164      */
   165     public void update() {
   166         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
   167     }
   169     @Override
   170     public void handleMessage(String event, JSONObject message) {
   171         final String id = message.optString("id");
   172         final String text = message.optString("text");
   173         final String iconURI = message.optString("iconURI");
   175         // Don't update the banner if the message doesn't have valid id and text.
   176         if (TextUtils.isEmpty(id) || TextUtils.isEmpty(text)) {
   177             return;
   178         }
   180         // Update the banner message on the UI thread.
   181         ThreadUtils.postToUiThread(new Runnable() {
   182             @Override
   183             public void run() {
   184                 // Store the current message id to pass back to JS in the view's OnClickListener.
   185                 setTag(id);
   186                 mTextView.setOriginalText(Html.fromHtml(text));
   188                 BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() {
   189                     @Override
   190                     public void onBitmapFound(final Drawable d) {
   191                         // Hide the image view if we don't have an icon to show.
   192                         if (d == null) {
   193                             mIconView.setVisibility(View.GONE);
   194                         } else {
   195                             mIconView.setImageDrawable(d);
   196                         }
   197                     }
   198                 });
   200                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Shown", id));
   202                 // Enable the banner after a message is set.
   203                 setEnabled(true);
   205                 // Animate the banner if it is currently active.
   206                 if (mActive) {
   207                     animateUp();
   208                 }
   209             }
   210         });
   211     }
   213     public void setActive(boolean active) {
   214         // No need to animate if not changing
   215         if (mActive == active) {
   216             return;
   217         }
   219         mActive = active;
   221         // Don't animate if the banner isn't enabled.
   222         if (!isEnabled()) {
   223             return;
   224         }
   226         if (active) {
   227             animateUp();
   228         } else {
   229             animateDown();
   230         }
   231     }
   233     private void ensureVisible() {
   234         // The banner visibility is set to GONE after it is animated off screen,
   235         // so we need to make it visible again.
   236         if (getVisibility() == View.GONE) {
   237             // Translate the banner off screen before setting it to VISIBLE.
   238             ViewHelper.setTranslationY(this, mHeight);
   239             setVisibility(View.VISIBLE);
   240         }
   241     }
   243     private void animateUp() {
   244         // Don't try to animate if the user swiped the banner down previously to hide it.
   245         if (mUserSwipedDown) {
   246             return;
   247         }
   249         ensureVisible();
   251         final PropertyAnimator animator = new PropertyAnimator(100);
   252         animator.attach(this, Property.TRANSLATION_Y, 0);
   253         animator.start();
   254     }
   256     private void animateDown() {
   257         if (ViewHelper.getTranslationY(this) == mHeight) {
   258             // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
   259             setVisibility(View.GONE);
   260             return;
   261         }
   263         final PropertyAnimator animator = new PropertyAnimator(100);
   264         animator.attach(this, Property.TRANSLATION_Y, mHeight);
   265         animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
   266             @Override
   267             public void onPropertyAnimationStart() {
   268             }
   270             @Override
   271             public void onPropertyAnimationEnd() {
   272                 // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
   273                 setVisibility(View.GONE);
   274             }
   275         });
   276         animator.start();
   277     }
   279     public void handleHomeTouch(MotionEvent event) {
   280         if (!mActive || !isEnabled() || mScrollingPages) {
   281             return;
   282         }
   284         ensureVisible();
   286         switch (event.getActionMasked()) {
   287             case MotionEvent.ACTION_DOWN: {
   288                 // Track the beginning of the touch
   289                 mTouchY = event.getRawY();
   290                 break;
   291             }
   293             case MotionEvent.ACTION_MOVE: {
   294                 final float curY = event.getRawY();
   295                 final float delta = mTouchY - curY;
   296                 mSnapBannerToTop = delta <= 0.0f;
   298                 float newTranslationY = ViewHelper.getTranslationY(this) + delta;
   300                 // Clamp the values to be between 0 and height.
   301                 if (newTranslationY < 0.0f) {
   302                     newTranslationY = 0.0f;
   303                 } else if (newTranslationY > mHeight) {
   304                     newTranslationY = mHeight;
   305                 }
   307                 // Don't change this value if it wasn't a significant movement
   308                 if (delta >= 10 || delta <= -10) {
   309                     mUserSwipedDown = (newTranslationY == mHeight);
   310                 }
   312                 ViewHelper.setTranslationY(this, newTranslationY);
   313                 mTouchY = curY;
   314                 break;
   315             }
   317             case MotionEvent.ACTION_UP:
   318             case MotionEvent.ACTION_CANCEL: {
   319                 mTouchY = -1;
   320                 if (mSnapBannerToTop) {
   321                     animateUp();
   322                 } else {
   323                     animateDown();
   324                     mUserSwipedDown = true;
   325                 }
   326                 break;
   327             }
   328         }
   329     }
   330 }

mercurial