Bug 1293790 - WIP: Implement Paged TopSites View
This uses a ViewPager, with each page containing a grid managed by a separate RecyclerView.
One main adapter splits the data into appropriately sized groups for each RecyclerView
to handle.
MozReview-Commit-ID: 9XGuw0NckD4
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
@@ -17,20 +17,24 @@ import android.widget.FrameLayout;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.home.HomeBanner;
import org.mozilla.gecko.home.HomeFragment;
import org.mozilla.gecko.home.HomeScreen;
import org.mozilla.gecko.home.SimpleCursorLoader;
+import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
public class ActivityStream extends FrameLayout implements HomeScreen {
private StreamRecyclerAdapter adapter;
+ private static final int LOADER_ID_HIGHLIGHTS = 0;
+ private static final int LOADER_ID_TOPSITES = 1;
+
public ActivityStream(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.as_content, this);
}
@Override
public boolean isVisible() {
@@ -66,26 +70,32 @@ public class ActivityStream extends Fram
}
@Override
public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData,
PropertyAnimator animator) {
// Signal to load data from storage as needed, compare with HomePager
RecyclerView rv = (RecyclerView) findViewById(R.id.activity_stream_main_recyclerview);
- adapter = new StreamRecyclerAdapter();
+ // TODO: we need to retrieve BrowserApp and pass it in as onUrlOpenListener. That will
+ // be simpler once we're a HomeFragment, but isn't so simple while we're still a View.
+ adapter = new StreamRecyclerAdapter(lm, null);
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(getContext()));
rv.setHasFixedSize(true);
- lm.initLoader(0, null, new CursorLoaderCallbacks());
+ CursorLoaderCallbacks callbacks = new CursorLoaderCallbacks();
+ lm.initLoader(LOADER_ID_HIGHLIGHTS, null, callbacks);
+ lm.initLoader(LOADER_ID_TOPSITES, null, callbacks);
}
@Override
public void unload() {
+ adapter.swapHighlightsCursor(null);
+ adapter.swapTopSitesCursor(null);
// Signal to clear data that has been loaded, compare with HomePager
}
/**
* This is a temporary cursor loader. We'll probably need a completely new query for AS,
* at that time we can switch to the new CursorLoader, as opposed to using our outdated
* SimpleCursorLoader.
*/
@@ -100,22 +110,37 @@ public class ActivityStream extends Fram
return GeckoProfile.get(context).getDB()
.getRecentHistory(context.getContentResolver(), 10);
}
}
private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return new HistoryLoader(getContext());
+ if (id == LOADER_ID_HIGHLIGHTS) {
+ return new HistoryLoader(getContext());
+ } else if (id == LOADER_ID_TOPSITES) {
+ return GeckoProfile.get(getContext()).getDB().getActivityStreamTopSites(getContext(),
+ TopSitesPagerAdapter.TOTAL_ITEMS);
+ } else {
+ throw new IllegalArgumentException("Can't handle loader id " + id);
+ }
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- adapter.swapCursor(data);
+ if (loader.getId() == LOADER_ID_HIGHLIGHTS) {
+ adapter.swapHighlightsCursor(data);
+ } else if (loader.getId() == LOADER_ID_TOPSITES) {
+ adapter.swapTopSitesCursor(data);
+ }
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
- adapter.swapCursor(null);
+ if (loader.getId() == LOADER_ID_HIGHLIGHTS) {
+ adapter.swapHighlightsCursor(null);
+ } else if (loader.getId() == LOADER_ID_TOPSITES) {
+ adapter.swapTopSitesCursor(null);
+ }
}
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
@@ -1,38 +1,50 @@
/* -*- 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.activitystream;
import android.database.Cursor;
+import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
public abstract class StreamItem extends RecyclerView.ViewHolder {
public StreamItem(View itemView) {
super(itemView);
}
public void bind(Cursor cursor) {
throw new IllegalStateException("Cannot bind " + this.getClass().getSimpleName());
}
public static class TopPanel extends StreamItem {
public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
+ private final ViewPager topSitesPager;
- public TopPanel(View itemView) {
+ public TopPanel(View itemView, HomePager.OnUrlOpenListener onUrlOpenListener) {
super(itemView);
+
+ topSitesPager = (ViewPager) itemView.findViewById(R.id.topsites_pager);
+ topSitesPager.setAdapter(new TopSitesPagerAdapter(itemView.getContext(), onUrlOpenListener));
+ }
+
+ @Override
+ public void bind(Cursor cursor) {
+ ((TopSitesPagerAdapter) topSitesPager.getAdapter()).swapCursor(cursor);
}
}
public static class BottomPanel extends StreamItem {
public static final int LAYOUT_ID = R.layout.activity_stream_main_bottompanel;
public BottomPanel(View itemView) {
super(itemView);
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
@@ -1,26 +1,39 @@
/* -*- 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.activitystream;
import android.database.Cursor;
+import android.support.v4.app.LoaderManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
+import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.activitystream.StreamItem.BottomPanel;
import org.mozilla.gecko.home.activitystream.StreamItem.CompactItem;
import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
+import java.lang.ref.WeakReference;
+
public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> {
private Cursor highlightsCursor;
+ private Cursor topSitesCursor;
+
+ private final WeakReference<LoaderManager> loaderManagerWeakReference;
+ private final HomePager.OnUrlOpenListener onUrlOpenListener;
+
+ StreamRecyclerAdapter(LoaderManager lm, HomePager.OnUrlOpenListener onUrlOpenListener) {
+ loaderManagerWeakReference = new WeakReference<>(lm);
+ this.onUrlOpenListener = onUrlOpenListener;
+ }
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TopPanel.LAYOUT_ID;
} else if (position == getItemCount() - 1) {
return BottomPanel.LAYOUT_ID;
} else {
@@ -33,17 +46,17 @@ public class StreamRecyclerAdapter exten
}
}
@Override
public StreamItem onCreateViewHolder(ViewGroup parent, final int type) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (type == TopPanel.LAYOUT_ID) {
- return new TopPanel(inflater.inflate(type, parent, false));
+ return new TopPanel(inflater.inflate(type, parent, false), onUrlOpenListener);
} else if (type == BottomPanel.LAYOUT_ID) {
return new BottomPanel(inflater.inflate(type, parent, false));
} else if (type == CompactItem.LAYOUT_ID) {
return new CompactItem(inflater.inflate(type, parent, false));
} else if (type == HighlightItem.LAYOUT_ID) {
return new HighlightItem(inflater.inflate(type, parent, false));
} else {
throw new IllegalStateException("Missing inflation for ViewType " + type);
@@ -66,29 +79,37 @@ public class StreamRecyclerAdapter exten
if (type == CompactItem.LAYOUT_ID ||
type == HighlightItem.LAYOUT_ID) {
final int cursorPosition = translatePositionToCursor(position);
highlightsCursor.moveToPosition(cursorPosition);
holder.bind(highlightsCursor);
+ } else if (type == TopPanel.LAYOUT_ID) {
+ holder.bind(topSitesCursor);
}
}
@Override
public int getItemCount() {
final int highlightsCount;
if (highlightsCursor != null) {
highlightsCount = highlightsCursor.getCount();
} else {
highlightsCount = 0;
}
return 2 + highlightsCount;
}
- public void swapCursor(Cursor cursor) {
+ public void swapHighlightsCursor(Cursor cursor) {
highlightsCursor = cursor;
notifyDataSetChanged();
}
+
+ public void swapTopSitesCursor(Cursor cursor) {
+ this.topSitesCursor = cursor;
+
+ notifyItemChanged(0);
+ }
}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/TopSitesRecyclerAdapter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-
-class TopSitesRecyclerAdapter extends RecyclerView.Adapter<TopSitesRecyclerAdapter.ViewHolder> {
-
- private final Context context;
- private final String[] items = {
- "FastMail",
- "Firefox",
- "Mozilla",
- "Hacker News",
- "Github",
- "YouTube",
- "Google Maps"
- };
-
- TopSitesRecyclerAdapter(Context context) {
- this.context = context;
- }
-
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = LayoutInflater
- .from(context)
- .inflate(R.layout.activity_stream_card_top_sites_item, parent, false);
- return new ViewHolder(v);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- holder.vLabel.setText(items[position]);
- }
-
- @Override
- public int getItemCount() {
- return items.length;
- }
-
- static class ViewHolder extends RecyclerView.ViewHolder {
- TextView vLabel;
- ViewHolder(View itemView) {
- super(itemView);
- vLabel = (TextView) itemView.findViewById(R.id.card_row_label);
- }
- }
-}
-
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
@@ -0,0 +1,60 @@
+/* -*- 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.activitystream.topsites;
+
+import android.database.Cursor;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.favicons.Favicons;
+import org.mozilla.gecko.home.UpdateViewFaviconLoadedListener;
+import org.mozilla.gecko.widget.FaviconView;
+
+import java.util.EnumSet;
+
+class TopSitesCard extends RecyclerView.ViewHolder {
+ private final FaviconView faviconView;
+
+ private final TextView title;
+ private final View menuButton;
+
+ private final UpdateViewFaviconLoadedListener mFaviconListener;
+
+ private String url;
+
+ private int mLoadFaviconJobId = Favicons.NOT_LOADING;
+
+ public TopSitesCard(CardView card) {
+ super(card);
+
+ faviconView = (FaviconView) card.findViewById(R.id.favicon);
+
+ title = (TextView) card.findViewById(R.id.title);
+ menuButton = card.findViewById(R.id.menu);
+
+ mFaviconListener = new UpdateViewFaviconLoadedListener(faviconView);
+
+ card.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ throw new IllegalStateException("foobar");
+ }
+ });
+ }
+
+ void bind(Cursor cursor) {
+ this.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
+
+ title.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE)));
+
+ Favicons.cancelFaviconLoad(mLoadFaviconJobId);
+
+ mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(faviconView.getContext(), url, mFaviconListener);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java
@@ -0,0 +1,50 @@
+/* -*- 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.activitystream.topsites;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.widget.RecyclerViewClickSupport;
+
+import java.util.EnumSet;
+
+public class TopSitesPage
+ extends RecyclerView
+ implements RecyclerViewClickSupport.OnItemClickListener {
+ public TopSitesPage(Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+
+ setLayoutManager(new GridLayoutManager(context, TopSitesPagerAdapter.GRID_WIDTH));
+
+ RecyclerViewClickSupport.addTo(this)
+ .setOnItemClickListener(this);
+ }
+
+ private HomePager.OnUrlOpenListener onUrlOpenListener = null;
+
+ public TopSitesPageAdapter getAdapter() {
+ return (TopSitesPageAdapter) super.getAdapter();
+ }
+
+ public void setOnUrlOpenListener(HomePager.OnUrlOpenListener onUrlOpenListener) {
+ this.onUrlOpenListener = onUrlOpenListener;
+ }
+
+ @Override
+ public void onItemClicked(RecyclerView recyclerView, int position, View v) {
+ if (onUrlOpenListener != null) {
+ final String url = getAdapter().getURLForPosition(position);
+
+ onUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
+ }
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
@@ -0,0 +1,119 @@
+/* -*- 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.activitystream.topsites;
+
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.support.annotation.UiThread;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+
+public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
+
+ /**
+ * Cursor wrapper that handles the offsets and limits that we expect.
+ * This allows most of our code to completely ignore the fact that we're only touching part
+ * of the cursor.
+ */
+ private static final class SubsetCursor extends CursorWrapper {
+ private final int start;
+ private final int count;
+
+ public SubsetCursor(Cursor cursor, int start, int maxCount) {
+ super(cursor);
+
+ this.start = start;
+
+ if (start + maxCount < cursor.getCount()) {
+ count = maxCount;
+ } else {
+ count = cursor.getCount() - start;
+ }
+ }
+
+ @Override
+ public boolean moveToPosition(int position) {
+ return super.moveToPosition(position + start);
+ }
+
+ @Override
+ public int getCount() {
+ return count;
+ }
+ }
+
+ private Cursor cursor;
+
+ /**
+ *
+ * @param cursor
+ * @param startIndex The first item that this topsites group should show. This item, and the following
+ * 3 items will be displayed by this adapter.
+ */
+ public void swapCursor(Cursor cursor, int startIndex) {
+ // assert startIndex < size?
+ if (startIndex >= cursor.getCount()) {
+ throw new IllegalArgumentException("startIndex must be within Cursor range");
+ }
+
+ if (cursor != null) {
+ this.cursor = new SubsetCursor(cursor, startIndex, TopSitesPagerAdapter.ITEMS_PER_PAGE);
+ } else {
+ this.cursor = null;
+ }
+
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onBindViewHolder(TopSitesCard holder, int position) {
+ cursor.moveToPosition(position);
+ holder.bind(cursor);
+ }
+
+ public TopSitesPageAdapter() {
+ setHasStableIds(true);
+ }
+
+ @Override
+ public TopSitesCard onCreateViewHolder(ViewGroup parent, int viewType) {
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+ final CardView card = (CardView) inflater.inflate(R.layout.activity_stream_topsites_card, parent, false);
+
+ return new TopSitesCard(card);
+ }
+
+ @UiThread
+ public String getURLForPosition(int position) {
+ cursor.moveToPosition(position);
+
+ return cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
+ }
+
+ @Override
+ public int getItemCount() {
+ if (cursor != null) {
+ return cursor.getCount();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ @UiThread
+ public long getItemId(int position) {
+ cursor.moveToPosition(position);
+
+ // The Combined View only contains pages that have been visited at least once, i.e. any
+ // page in the TopSites query will contain a HISTORY_ID. _ID however will be 0 for all rows.
+ return cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
@@ -0,0 +1,111 @@
+/* -*- 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.activitystream.topsites;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.view.PagerAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.home.HomePager;
+
+import java.util.LinkedList;
+
+/**
+ * The primary / top-level TopSites adapter: it handles the ViewPager, and also handles
+ * all lower-level Adapters that populate the individual topsite items.
+ */
+public class TopSitesPagerAdapter extends PagerAdapter {
+ // Note: because of RecyclerView limitations we need to also adjust the layout height when
+ // GRID_HEIGHT is changed.
+ public static final int GRID_HEIGHT = 1;
+ public static final int GRID_WIDTH = 4;
+ public static final int PAGES = 4;
+
+ public static final int ITEMS_PER_PAGE = GRID_HEIGHT * GRID_WIDTH;
+ public static final int TOTAL_ITEMS = ITEMS_PER_PAGE * PAGES;
+
+ private LinkedList<TopSitesPage> pages = new LinkedList<>();
+
+ private final Context context;
+ private final HomePager.OnUrlOpenListener onUrlOpenListener;
+
+ private int count = 0;
+
+ public TopSitesPagerAdapter(Context context, HomePager.OnUrlOpenListener onUrlOpenListener) {
+ this.context = context;
+ this.onUrlOpenListener = onUrlOpenListener;
+ }
+
+ @Override
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ TopSitesPage page = pages.get(position);
+
+ container.addView(page);
+
+ return page;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ container.removeView((View) object);
+ }
+
+ public void swapCursor(Cursor cursor) {
+ final int oldPages = getCount();
+
+ // Divide while rounding up: 0 items = 0 pages, 1-ITEMS_PER_PAGE items = 1 page, etc.
+ if (cursor != null) {
+ count = (cursor.getCount() - 1) / ITEMS_PER_PAGE + 1;
+ } else {
+ count = 0;
+ }
+
+ final int pageDelta = count - oldPages;
+
+ if (pageDelta > 0) {
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ for (int i = 0; i < pageDelta; i++) {
+ final TopSitesPage page = (TopSitesPage) inflater.inflate(R.layout.activity_stream_topsites_page, null, false);
+
+ page.setOnUrlOpenListener(onUrlOpenListener);
+ page.setAdapter(new TopSitesPageAdapter());
+ pages.add(page);
+ }
+ } else if (pageDelta < 0) {
+ for (int i = 0; i > pageDelta; i--) {
+ final TopSitesPage page = pages.getLast();
+
+ // Ensure the page doesn't use the old/invalid cursor anymore
+ page.getAdapter().swapCursor(null, 0);
+
+ pages.removeLast();
+ }
+ } else {
+ // do nothing: we will be updating all the pages below
+ }
+
+ int startIndex = 0;
+ for (TopSitesPage page : pages) {
+ page.getAdapter().swapCursor(cursor, startIndex);
+ startIndex += ITEMS_PER_PAGE;
+ }
+
+ notifyDataSetChanged();
+ }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -427,17 +427,20 @@ gbjar.sources += ['java/org/mozilla/geck
'GlobalHistory.java',
'GuestSession.java',
'health/HealthRecorder.java',
'health/SessionInformation.java',
'health/StubbedHealthRecorder.java',
'home/activitystream/ActivityStream.java',
'home/activitystream/StreamItem.java',
'home/activitystream/StreamRecyclerAdapter.java',
- 'home/activitystream/TopSitesRecyclerAdapter.java',
+ 'home/activitystream/topsites/TopSitesCard.java',
+ 'home/activitystream/topsites/TopSitesPage.java',
+ 'home/activitystream/topsites/TopSitesPageAdapter.java',
+ 'home/activitystream/topsites/TopSitesPagerAdapter.java',
'home/BookmarkFolderView.java',
'home/BookmarkScreenshotRow.java',
'home/BookmarksListAdapter.java',
'home/BookmarksListView.java',
'home/BookmarksPanel.java',
'home/BrowserSearch.java',
'home/ClientsAdapter.java',
'home/CombinedHistoryAdapter.java',
--- a/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
+++ b/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
@@ -26,31 +26,31 @@
android:layout_alignParentRight="true"
android:textAllCaps="true"
android:textColor="@android:color/holo_orange_dark"
android:textSize="14sp"
android:text="@string/activity_stream_more"
tools:text="More"
android:layout_alignBottom="@+id/title_topsites"/>
- <android.support.v7.widget.RecyclerView
+ <android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="115dp"
- android:id="@+id/android.support.v7.widget.RecyclerView"
+ android:id="@+id/topsites_pager"
android:layout_below="@+id/title_topsites"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/>
<TextView
android:id="@+id/title_highlights"
android:text="@string/activity_stream_highlights"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
- android:layout_below="@+id/android.support.v7.widget.RecyclerView"
+ android:layout_below="@+id/topsites_pager"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@+id/more_highlights"
android:layout_toStartOf="@+id/more_highlights"/>
<TextView
android:id="@+id/more_highlights"
android:layout_width="wrap_content"
rename from mobile/android/base/resources/layout/activity_stream_card_top_sites_item.xml
rename to mobile/android/base/resources/layout/activity_stream_topsites_card.xml
--- a/mobile/android/base/resources/layout/activity_stream_card_top_sites_item.xml
+++ b/mobile/android/base/resources/layout/activity_stream_topsites_card.xml
@@ -1,46 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.CardView
+<org.mozilla.gecko.widget.FilledCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- android:layout_marginRight="5dp"
- android:layout_marginEnd="5dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:orientation="vertical"
- android:layout_width="90dp"
- android:layout_height="match_parent">
+ android:layout_width="wrap_content"
+ android:layout_height="115dp"
+ android:layout_margin="1dp">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:baselineAligned="false">
-
- <FrameLayout
+ <org.mozilla.gecko.widget.FaviconView
+ android:id="@+id/favicon"
android:layout_width="match_parent"
- android:background="@color/disabled_grey"
- android:layout_height="70dp">
+ android:layout_height="wrap_content"
+ android:layout_above="@+id/title"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="center"
+ tools:background="@drawable/favicon_globe"/>
- <ImageView
- android:src="@drawable/favicon_globe"
- android:scaleType="fitCenter"
- android:layout_gravity="center"
- android:layout_width="40dp"
- android:layout_height="40dp"/>
- </FrameLayout>
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:ellipsize="end"
+ android:gravity="center"
+ android:lines="1"
+ android:padding="4dp"
+ android:textColor="@android:color/black"
+ tools:text="Lorem Ipsum here is a title"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"/>
- <TextView
- android:id="@+id/card_row_label"
- tools:text="Firefox"
- android:textSize="10sp"
- android:textStyle="bold"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </FrameLayout>
- </LinearLayout>
-</android.support.v7.widget.CardView>
\ No newline at end of file
+ <ImageView
+ android:id="@+id/menu_button"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:layout_gravity="right|top"
+ android:padding="6dp"
+ android:src="@drawable/menu"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"/>
+
+ </RelativeLayout>
+</org.mozilla.gecko.widget.FilledCardView>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/activity_stream_topsites_page.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<org.mozilla.gecko.home.activitystream.topsites.TopSitesPage xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>