summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java315
1 files changed, 315 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
new file mode 100644
index 000000000..10f5db39e
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
@@ -0,0 +1,315 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.home;
+
+import org.json.JSONObject;
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.animation.PropertyAnimator.Property;
+import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.util.ResourceDrawableUtils;
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.widget.EllipsisTextView;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class HomeBanner extends LinearLayout
+ implements GeckoEventListener {
+ private static final String LOGTAG = "GeckoHomeBanner";
+
+ // Used for tracking scroll length
+ private float mTouchY = -1;
+
+ // Used to detect for upwards scroll to push banner all the way up
+ private boolean mSnapBannerToTop;
+
+ // Tracks whether or not the banner should be shown on the current panel.
+ private boolean mActive;
+
+ // The user is currently swiping between HomePager pages
+ private boolean mScrollingPages;
+
+ // Tracks whether the user swiped the banner down, preventing us from autoshowing when the user
+ // switches back to the default page.
+ private boolean mUserSwipedDown;
+
+ // We must use this custom TextView to address an issue on 2.3 and lower where ellipsized text
+ // will not wrap more than 2 lines.
+ private final EllipsisTextView mTextView;
+ private final ImageView mIconView;
+
+ // The height of the banner view.
+ private final float mHeight;
+
+ // Listener that gets called when the banner is dismissed from the close button.
+ private OnDismissListener mOnDismissListener;
+
+ public interface OnDismissListener {
+ public void onDismiss();
+ }
+
+ public HomeBanner(Context context) {
+ this(context, null);
+ }
+
+ public HomeBanner(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ LayoutInflater.from(context).inflate(R.layout.home_banner_content, this);
+
+ mTextView = (EllipsisTextView) findViewById(R.id.text);
+ mIconView = (ImageView) findViewById(R.id.icon);
+
+ mHeight = getResources().getDimensionPixelSize(R.dimen.home_banner_height);
+
+ // Disable the banner until a message is set.
+ setEnabled(false);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Tapping on the close button will ensure that the banner is never
+ // showed again on this session.
+ final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
+
+ // The drawable should have 50% opacity.
+ closeButton.getDrawable().setAlpha(127);
+
+ closeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ HomeBanner.this.dismiss();
+
+ // Send the current message id back to JS.
+ GeckoAppShell.notifyObservers("HomeBanner:Dismiss", (String) getTag());
+ }
+ });
+
+ setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ HomeBanner.this.dismiss();
+
+ // Send the current message id back to JS.
+ GeckoAppShell.notifyObservers("HomeBanner:Click", (String) getTag());
+ }
+ });
+
+ GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "HomeBanner:Data");
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "HomeBanner:Data");
+ }
+
+ public void setScrollingPages(boolean scrollingPages) {
+ mScrollingPages = scrollingPages;
+ }
+
+ public void setOnDismissListener(OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ /**
+ * Hides and disables the banner.
+ */
+ private void dismiss() {
+ setVisibility(View.GONE);
+ setEnabled(false);
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
+ }
+
+ /**
+ * Sends a message to gecko to request a new banner message. UI is updated in handleMessage.
+ */
+ public void update() {
+ GeckoAppShell.notifyObservers("HomeBanner:Get", null);
+ }
+
+ @Override
+ public void handleMessage(String event, JSONObject message) {
+ final String id = message.optString("id");
+ final String text = message.optString("text");
+ final String iconURI = message.optString("iconURI");
+
+ // Don't update the banner if the message doesn't have valid id and text.
+ if (TextUtils.isEmpty(id) || TextUtils.isEmpty(text)) {
+ return;
+ }
+
+ // Update the banner message on the UI thread.
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // Store the current message id to pass back to JS in the view's OnClickListener.
+ setTag(id);
+ mTextView.setOriginalText(Html.fromHtml(text));
+
+ ResourceDrawableUtils.getDrawable(getContext(), iconURI, new ResourceDrawableUtils.BitmapLoader() {
+ @Override
+ public void onBitmapFound(final Drawable d) {
+ // Hide the image view if we don't have an icon to show.
+ if (d == null) {
+ mIconView.setVisibility(View.GONE);
+ } else {
+ mIconView.setImageDrawable(d);
+ mIconView.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
+ GeckoAppShell.notifyObservers("HomeBanner:Shown", id);
+
+ // Enable the banner after a message is set.
+ setEnabled(true);
+
+ // Animate the banner if it is currently active.
+ if (mActive) {
+ animateUp();
+ }
+ }
+ });
+ }
+
+ public void setActive(boolean active) {
+ // No need to animate if not changing
+ if (mActive == active) {
+ return;
+ }
+
+ mActive = active;
+
+ // Don't animate if the banner isn't enabled.
+ if (!isEnabled()) {
+ return;
+ }
+
+ if (active) {
+ animateUp();
+ } else {
+ animateDown();
+ }
+ }
+
+ private void ensureVisible() {
+ // The banner visibility is set to GONE after it is animated off screen,
+ // so we need to make it visible again.
+ if (getVisibility() == View.GONE) {
+ // Translate the banner off screen before setting it to VISIBLE.
+ ViewHelper.setTranslationY(this, mHeight);
+ setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void animateUp() {
+ // Don't try to animate if the user swiped the banner down previously to hide it.
+ if (mUserSwipedDown) {
+ return;
+ }
+
+ ensureVisible();
+
+ final PropertyAnimator animator = new PropertyAnimator(100);
+ animator.attach(this, Property.TRANSLATION_Y, 0);
+ animator.start();
+ }
+
+ private void animateDown() {
+ if (ViewHelper.getTranslationY(this) == mHeight) {
+ // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
+ setVisibility(View.GONE);
+ return;
+ }
+
+ final PropertyAnimator animator = new PropertyAnimator(100);
+ animator.attach(this, Property.TRANSLATION_Y, mHeight);
+ animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
+ @Override
+ public void onPropertyAnimationStart() {
+ }
+
+ @Override
+ public void onPropertyAnimationEnd() {
+ // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
+ setVisibility(View.GONE);
+ }
+ });
+ animator.start();
+ }
+
+ public void handleHomeTouch(MotionEvent event) {
+ if (!mActive || !isEnabled() || mScrollingPages) {
+ return;
+ }
+
+ ensureVisible();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Track the beginning of the touch
+ mTouchY = event.getRawY();
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ final float curY = event.getRawY();
+ final float delta = mTouchY - curY;
+ mSnapBannerToTop = delta <= 0.0f;
+
+ float newTranslationY = ViewHelper.getTranslationY(this) + delta;
+
+ // Clamp the values to be between 0 and height.
+ if (newTranslationY < 0.0f) {
+ newTranslationY = 0.0f;
+ } else if (newTranslationY > mHeight) {
+ newTranslationY = mHeight;
+ }
+
+ // Don't change this value if it wasn't a significant movement
+ if (delta >= 10 || delta <= -10) {
+ mUserSwipedDown = (newTranslationY == mHeight);
+ }
+
+ ViewHelper.setTranslationY(this, newTranslationY);
+ mTouchY = curY;
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ mTouchY = -1;
+ if (mSnapBannerToTop) {
+ animateUp();
+ } else {
+ animateDown();
+ mUserSwipedDown = true;
+ }
+ break;
+ }
+ }
+ }
+}