diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index c8d882837739..34b564c0c3d0 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -24,6 +24,7 @@ import org.mozilla.gecko.health.BrowserHealthReporter; import org.mozilla.gecko.health.HealthRecorder; import org.mozilla.gecko.health.SessionInformation; import org.mozilla.gecko.home.BrowserSearch; +import org.mozilla.gecko.home.HomeBanner; import org.mozilla.gecko.home.HomeConfigInvalidator; import org.mozilla.gecko.home.HomePager; import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; @@ -1678,6 +1679,9 @@ abstract public class BrowserApp extends GeckoApp if (mHomePager == null) { final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub); mHomePager = (HomePager) homePagerStub.inflate(); + + HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner); + mHomePager.setBanner(homeBanner); } mHomePagerContainer.setVisibility(View.VISIBLE); diff --git a/mobile/android/base/home/HomeBanner.java b/mobile/android/base/home/HomeBanner.java index fa429536e78b..7d08b0667b48 100644 --- a/mobile/android/base/home/HomeBanner.java +++ b/mobile/android/base/home/HomeBanner.java @@ -5,6 +5,10 @@ package org.mozilla.gecko.home; +import org.mozilla.gecko.animation.PropertyAnimator; +import org.mozilla.gecko.animation.PropertyAnimator.Property; +import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener; +import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.R; @@ -23,6 +27,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; @@ -33,6 +38,22 @@ 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 if the banner has been enabled by HomePager to avoid race conditions. + private boolean mEnabled = false; + + // The user is currently swiping between HomePager pages + private boolean mScrollingPages = false; + + // Tracks whether the user swiped the banner down, preventing us from autoshowing when the user + // switches back to the default page. + private boolean mUserSwipedDown = false; + public HomeBanner(Context context) { this(context, null); } @@ -82,9 +103,9 @@ public class HomeBanner extends LinearLayout GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this); } - public boolean isDismissed() { - return (getVisibility() == View.GONE); - } + public void setScrollingPages(boolean scrollingPages) { + mScrollingPages = scrollingPages; + } @Override public void handleMessage(String event, JSONObject message) { @@ -101,7 +122,8 @@ public class HomeBanner extends LinearLayout @Override public void run() { textView.setText(text); - setVisibility(View.VISIBLE); + setVisibility(VISIBLE); + animateUp(); } }); } catch (JSONException e) { @@ -137,4 +159,102 @@ public class HomeBanner extends LinearLayout } }); } + + public void setEnabled(boolean enabled) { + // No need to animate if not changing + if (mEnabled == enabled) { + return; + } + + mEnabled = enabled; + if (enabled) { + animateUp(); + } else { + animateDown(); + } + } + + private void animateUp() { + // Check to make sure that message has been received and the banner has been enabled. + // Necessary to avoid race conditions between show() and handleMessage() calls. + TextView textView = (TextView) findViewById(R.id.text); + if (!mEnabled || TextUtils.isEmpty(textView.getText()) || mUserSwipedDown) { + return; + } + + // No need to animate if already translated. + if (ViewHelper.getTranslationY(this) == 0) { + return; + } + + final PropertyAnimator animator = new PropertyAnimator(100); + animator.attach(this, Property.TRANSLATION_Y, 0); + animator.start(); + } + + private void animateDown() { + // No need to animate if already translated or gone. + if (ViewHelper.getTranslationY(this) == getHeight()) { + return; + } + + final PropertyAnimator animator = new PropertyAnimator(100); + animator.attach(this, Property.TRANSLATION_Y, getHeight()); + animator.start(); + } + + public void handleHomeTouch(MotionEvent event) { + if (!mEnabled || getVisibility() == GONE || mScrollingPages) { + return; + } + + 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; + + final float height = getHeight(); + 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 > height) { + newTranslationY = height; + } + + // Don't change this value if it wasn't a significant movement + if (delta >= 10 || delta <= -10) { + mUserSwipedDown = newTranslationY == height; + } + + ViewHelper.setTranslationY(this, newTranslationY); + mTouchY = curY; + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + mTouchY = -1; + final float y = ViewHelper.getTranslationY(this); + final float height = getHeight(); + if (y > 0.0f && y < height) { + if (mSnapBannerToTop) { + animateUp(); + } else { + animateDown(); + mUserSwipedDown = true; + } + } + break; + } + } + } } diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index 8a467e398957..5d78c2f4ceff 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -41,6 +41,8 @@ public class HomePager extends ViewPager { private volatile boolean mLoaded; private Decor mDecor; private View mTabStrip; + private HomeBanner mHomeBanner; + private int mDefaultPageIndex = -1; private final OnAddPanelListener mAddPanelListener; @@ -125,6 +127,7 @@ public class HomePager extends ViewPager { setFocusableInTouchMode(true); mOriginalBackground = getBackground(); + setOnPageChangeListener(new PageChangeListener()); } @Override @@ -140,21 +143,6 @@ public class HomePager extends ViewPager { setCurrentItem(index, true); } }); - - setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageSelected(int position) { - mDecor.onPageSelected(position); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - - @Override - public void onPageScrollStateChanged(int state) { } - }); } else if (child instanceof HomePagerTabStrip) { mTabStrip = child; } @@ -247,6 +235,19 @@ public class HomePager extends ViewPager { return super.onInterceptTouchEvent(event); } + public void setBanner(HomeBanner banner) { + mHomeBanner = banner; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (mHomeBanner != null) { + mHomeBanner.handleHomeTouch(event); + } + + return super.dispatchTouchEvent(event); + } + private void updateUiFromPanelConfigs(List panelConfigs) { // We only care about the adapter if HomePager is currently // loaded, which means it's visible in the activity. @@ -303,6 +304,7 @@ public class HomePager extends ViewPager { for (int i = 0; i < count; i++) { final PanelConfig panelConfig = enabledPanels.get(i); if (panelConfig.isDefault()) { + mDefaultPageIndex = i; setCurrentItem(i, false); break; } @@ -325,4 +327,31 @@ public class HomePager extends ViewPager { public void onLoaderReset(Loader> loader) { } } + + private class PageChangeListener implements ViewPager.OnPageChangeListener { + @Override + public void onPageSelected(int position) { + if (mDecor != null) { + mDecor.onPageSelected(position); + } + + if (mHomeBanner != null) { + mHomeBanner.setEnabled(position == mDefaultPageIndex); + } + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + if (mDecor != null) { + mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + + if (mHomeBanner != null) { + mHomeBanner.setScrollingPages(positionOffsetPixels != 0); + } + } + + @Override + public void onPageScrollStateChanged(int state) { } + } } diff --git a/mobile/android/base/resources/layout/gecko_app.xml b/mobile/android/base/resources/layout/gecko_app.xml index 12b00283d440..c77a2a7b9482 100644 --- a/mobile/android/base/resources/layout/gecko_app.xml +++ b/mobile/android/base/resources/layout/gecko_app.xml @@ -44,7 +44,8 @@ android:gravity="center_vertical" android:visibility="gone" android:clickable="true" - android:focusable="true"/> + android:focusable="true" + android:translationY="@dimen/home_banner_height"/>