Bug 960359 - Refactor HomeBanner out of TopSitesPanel and display on default page. r=mleibovic

This commit is contained in:
Joshua Dover 2014-01-29 14:07:05 -08:00
parent f25709861f
commit f5d2b6efd3
8 changed files with 251 additions and 148 deletions

View File

@ -1657,7 +1657,7 @@ abstract public class BrowserApp extends GeckoApp
if (mHomePager == null) {
final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
mHomePager = (HomePager) homePagerStub.inflate();
mHomePager = (HomePager) homePagerStub.inflate().findViewById(R.id.home_pager);
}
mHomePager.show(getSupportLoaderManager(),

View File

@ -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";
final TextView mTextView;
final ImageView mIconView;
final ImageButton mCloseButton;
// 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;
// Used so that we don't move the banner when scrolling between pages
private boolean mScrollingPages = false;
// User has dismissed the banner using the close button
private boolean mDismissed = false;
public HomeBanner(Context context) {
this(context, null);
}
@ -41,23 +62,18 @@ public class HomeBanner extends LinearLayout
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.home_banner, this);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mTextView = (TextView) findViewById(R.id.text);
mIconView = (ImageView) findViewById(R.id.icon);
mCloseButton = (ImageButton) findViewById(R.id.close);
mCloseButton.getDrawable().setAlpha(127);
// 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() {
mCloseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
HomeBanner.this.setVisibility(View.GONE);
animateDown();
mDismissed = true;
}
});
@ -65,54 +81,66 @@ public class HomeBanner extends LinearLayout
@Override
public void onClick(View v) {
// Send the current message id back to JS.
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
GeckoAppShell.sendEventToGecko(
GeckoEvent.createBroadcastEvent("HomeBanner:Click",(String) getTag()));
}
});
}
@Override
public void onAttachedToWindow() {
GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
}
}
public boolean isDismissed() {
return (getVisibility() == View.GONE);
public void showBanner() {
if (!mDismissed) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
}
}
public void hideBanner() {
animateDown();
}
public void setScrollingPages(boolean scrollingPages) {
mScrollingPages = scrollingPages;
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
// Store the current message id to pass back to JS in the view's OnClickListener.
setTag(message.getString("id"));
// Display styled text from an HTML string.
final Spanned text = Html.fromHtml(message.getString("text"));
final TextView textView = (TextView) findViewById(R.id.text);
// Update the banner message on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
textView.setText(text);
setVisibility(View.VISIBLE);
public void handleMessage(final String event, final JSONObject message) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
try {
// Store the current message id to pass back to JS in the view's OnClickListener.
setTag(message.getString("id"));
setText(message.getString("text"));
setIcon(message.optString("iconURI"));
animateUp();
} catch (JSONException e) {
Log.e(LOGTAG, "Exception handling " + event + " message", e);
}
});
} catch (JSONException e) {
Log.e(LOGTAG, "Exception handling " + event + " message", e);
return;
}
}
});
}
final String iconURI = message.optString("iconURI");
final ImageView iconView = (ImageView) findViewById(R.id.icon);
private void setText(String text) {
// Display styled text from an HTML string.
final Spanned html = Html.fromHtml(text);
// Update the banner message on the UI thread.
mTextView.setText(html);
}
private void setIcon(String iconURI) {
if (TextUtils.isEmpty(iconURI)) {
// Hide the image view if we don't have an icon to show.
iconView.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
return;
}
@ -121,18 +149,102 @@ public class HomeBanner extends LinearLayout
public void onBitmapFound(final Drawable d) {
// Bail if getDrawable doesn't find anything.
if (d == null) {
iconView.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
return;
}
// Update the banner icon on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
iconView.setImageDrawable(d);
}
});
// Update the banner icon
mIconView.setImageDrawable(d);
}
});
}
private void animateDown() {
// No need to animate if already translated.
if (getVisibility() == GONE && ViewHelper.getTranslationY(this) == getHeight()) {
return;
}
final PropertyAnimator animator = new PropertyAnimator(100);
animator.attach(this, Property.TRANSLATION_Y, getHeight());
animator.start();
animator.addPropertyAnimationListener(new PropertyAnimationListener() {
@Override
public void onPropertyAnimationStart() {}
public void onPropertyAnimationEnd() {
HomeBanner.this.setVisibility(GONE);
}
});
}
private void animateUp() {
// No need to animate if already translated.
if (getVisibility() == VISIBLE && ViewHelper.getTranslationY(this) == 0) {
return;
}
setVisibility(View.VISIBLE);
final PropertyAnimator animator = new PropertyAnimator(100);
animator.attach(this, Property.TRANSLATION_Y, 0);
animator.start();
}
/**
* Touches to the HomePager are forwarded here to handle the hiding / showing of the banner
* on scroll.
*/
public void handleHomeTouch(MotionEvent event) {
if (mDismissed || mScrollingPages) {
return;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mTouchY = event.getRawY();
break;
}
case MotionEvent.ACTION_MOVE: {
// There is a chance that we won't receive ACTION_DOWN, if the touch event
// actually started on the Grid instead of the List. Treat this as first event.
if (mTouchY == -1) {
mTouchY = event.getRawY();
return;
}
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;
}
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();
}
}
break;
}
}
}
}

View File

@ -24,8 +24,10 @@ import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.view.ViewGroup.LayoutParams;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.View;
import java.util.ArrayList;
@ -33,6 +35,7 @@ import java.util.EnumSet;
import java.util.List;
public class HomePager extends ViewPager {
private static final String LOGTAG = "GeckoHomePager";
private static final int LOADER_ID_CONFIG = 0;
@ -40,6 +43,7 @@ public class HomePager extends ViewPager {
private volatile boolean mLoaded;
private Decor mDecor;
private View mTabStrip;
private HomeBanner mHomeBanner;
private final OnAddPanelListener mAddPanelListener;
@ -47,6 +51,7 @@ public class HomePager extends ViewPager {
private ConfigLoaderCallbacks mConfigLoaderCallbacks;
private String mInitialPanelId;
private int mDefaultPanelIndex;
// Whether or not we need to restart the loader when we show the HomePager.
private boolean mRestartLoader;
@ -238,6 +243,10 @@ public class HomePager extends ViewPager {
PropertyAnimator.Property.ALPHA,
1.0f);
}
// Setup banner and decor listeners
mHomeBanner = (HomeBanner) ((ViewGroup) getParent()).findViewById(R.id.home_banner);
setOnPageChangeListener(new HomePagerOnPageChangeListener());
}
/**
@ -249,6 +258,15 @@ public class HomePager extends ViewPager {
setAdapter(null);
}
@Override
public void setVisibility(int visibility) {
// Ensure that no decorations are overlaying the mainlayout
if (mHomeBanner != null) {
mHomeBanner.setVisibility(visibility);
}
super.setVisibility(visibility);
}
/**
* Determines whether the pager is visible.
*
@ -268,6 +286,13 @@ public class HomePager extends ViewPager {
if (mDecor != null) {
mDecor.onPageSelected(item);
}
if (mHomeBanner != null) {
if (item == mDefaultPanelIndex) {
mHomeBanner.showBanner();
} else {
mHomeBanner.hideBanner();
}
}
}
@Override
@ -280,6 +305,16 @@ public class HomePager extends ViewPager {
return super.onInterceptTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// Get touches to pages, pass to banner, and forward to pages.
if (mHomeBanner != null) {
mHomeBanner.handleHomeTouch(event);
}
return super.dispatchTouchEvent(event);
}
private void updateUiFromPanelConfigs(List<PanelConfig> panelConfigs) {
// We only care about the adapter if HomePager is currently
// loaded, which means it's visible in the activity.
@ -303,6 +338,9 @@ public class HomePager extends ViewPager {
for (PanelConfig panelConfig : panelConfigs) {
if (!panelConfig.isDisabled()) {
enabledPanels.add(panelConfig);
if (panelConfig.isDefault()) {
mDefaultPanelIndex = enabledPanels.size() - 1;
}
}
}
@ -348,4 +386,35 @@ public class HomePager extends ViewPager {
public void onLoaderReset(Loader<List<PanelConfig>> loader) {
}
}
private class HomePagerOnPageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageSelected(int position) {
if (mDecor != null) {
mDecor.onPageSelected(position);
}
if (mHomeBanner != null) {
if (position == mDefaultPanelIndex) {
mHomeBanner.showBanner();
} else {
mHomeBanner.hideBanner();
}
}
}
@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) { }
}
}

View File

@ -87,15 +87,6 @@ public class TopSitesPanel extends HomeFragment {
// Grid of top sites
private TopSitesGridView mGrid;
// Banner to show snippets.
private HomeBanner mBanner;
// Raw Y value of the last event that happened on the list view.
private float mListTouchY = -1;
// Scrolling direction of the banner.
private boolean mSnapBannerToTop;
// Callbacks used for the search and favicon cursor loaders
private CursorLoaderCallbacks mCursorLoaderCallbacks;
@ -207,15 +198,6 @@ public class TopSitesPanel extends HomeFragment {
registerForContextMenu(mList);
registerForContextMenu(mGrid);
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
mList.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
TopSitesPanel.this.handleListTouchEvent(event);
return false;
}
});
}
@Override
@ -454,60 +436,6 @@ public class TopSitesPanel extends HomeFragment {
}
}
private void handleListTouchEvent(MotionEvent event) {
// Ignore the event if the banner is hidden for this session.
if (mBanner.isDismissed()) {
return;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mListTouchY = event.getRawY();
break;
}
case MotionEvent.ACTION_MOVE: {
// There is a chance that we won't receive ACTION_DOWN, if the touch event
// actually started on the Grid instead of the List. Treat this as first event.
if (mListTouchY == -1) {
mListTouchY = event.getRawY();
return;
}
final float curY = event.getRawY();
final float delta = mListTouchY - curY;
mSnapBannerToTop = (delta > 0.0f) ? false : true;
final float height = mBanner.getHeight();
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
// Clamp the values to be between 0 and height.
if (newTranslationY < 0.0f) {
newTranslationY = 0.0f;
} else if (newTranslationY > height) {
newTranslationY = height;
}
ViewHelper.setTranslationY(mBanner, newTranslationY);
mListTouchY = curY;
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mListTouchY = -1;
final float y = ViewHelper.getTranslationY(mBanner);
final float height = mBanner.getHeight();
if (y > 0.0f && y < height) {
final PropertyAnimator animator = new PropertyAnimator(100);
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
animator.start();
}
break;
}
}
}
private void updateUiFromCursor(Cursor c) {
mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
}

View File

@ -14,7 +14,8 @@
android:background="@android:color/white"
android:visibility="gone">
<org.mozilla.gecko.home.TabMenuStrip android:layout_width="fill_parent"
<org.mozilla.gecko.home.TabMenuStrip android:id="@+id/tablet_menu_strip"
android:layout_width="fill_parent"
android:layout_height="32dip"
android:background="@color/background_light"
android:layout_gravity="top"

View File

@ -36,6 +36,16 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
style="@style/Widget.HomeBanner"
android:layout_width="fill_parent"
android:layout_height="@dimen/home_banner_height"
android:background="@drawable/home_banner"
android:layout_gravity="bottom"
android:gravity="center_vertical"
android:visibility="gone"
android:clickable="true"
android:focusable="true"/>
</FrameLayout>

View File

@ -14,7 +14,8 @@
android:background="@android:color/white"
android:visibility="gone">
<org.mozilla.gecko.home.HomePagerTabStrip android:layout_width="fill_parent"
<org.mozilla.gecko.home.HomePagerTabStrip android:id="@+id/phone_menu_strip"
android:layout_width="fill_parent"
android:layout_height="32dip"
android:layout_gravity="top"
android:gravity="bottom"

View File

@ -3,26 +3,8 @@
- 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/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<org.mozilla.gecko.home.HomeListView
android:id="@+id/list"
style="@style/Widget.TopSitesListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
style="@style/Widget.HomeBanner"
android:layout_width="fill_parent"
android:layout_height="@dimen/home_banner_height"
android:background="@drawable/home_banner"
android:layout_gravity="bottom"
android:gravity="center_vertical"
android:visibility="gone"
android:clickable="true"
android:focusable="true"/>
</FrameLayout>
<org.mozilla.gecko.home.HomeListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list"
style="@style/Widget.TopSitesListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />