copy from mobile/android/app/src/main/res/drawable/ic_as_bookmarked.xml
copy to mobile/android/app/src/main/res/drawable/ic_as_trending.xml
--- a/mobile/android/app/src/main/res/drawable/ic_as_bookmarked.xml
+++ b/mobile/android/app/src/main/res/drawable/ic_as_trending.xml
@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportWidth="12.0"
+ android:viewportHeight="12.0">
<path
android:fillColor="@color/activity_stream_icon"
- android:pathData="M12.01,1a1.34,1.34 0,0 0,-1.085 0.89l-2.747,5.67 -5.877,0.99c-1.345,0.22 -1.679,1.19 -0.744,2.15l4.16,4.49 -0.969,6.31c-0.147,0.94 0.242,1.5 0.925,1.5a1.986,1.986 0,0 0,0.891 -0.25l5.457,-2.93 5.521,2.93a1.993,1.993 0,0 0,0.892 0.25c0.683,0 1.07,-0.56 0.926,-1.5l-0.966,-6.31 4.111,-4.49c0.936,-0.96 0.6,-1.93 -0.744,-2.15l-5.789,-0.99 -2.877,-5.67a1.339,1.339 0,0 0,-1.085 -0.89h0Z"/>
+ android:pathData="M4.97.151l-2.819,6.5A.25.25,0,0,0,2.381,7H4.029a.25.25,0,0,1,.225.359L2,12,9.4,5.437A.25.25,0,0,0,9.234,5H6.791a.25.25,0,0,1-.19-.412L10.15.412A.25.25,0,0,0,9.959,0H5.2A.25.25,0,0,0,4.97.151Z"/>
</vector>
--- a/mobile/android/app/src/main/res/layout/activity_stream_main_highlightstitle.xml
+++ b/mobile/android/app/src/main/res/layout/activity_stream_main_highlightstitle.xml
@@ -5,14 +5,13 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title_highlights"
android:layout_marginLeft="@dimen/activity_stream_base_margin"
android:layout_marginStart="@dimen/activity_stream_base_margin"
android:layout_marginTop="@dimen/activity_stream_base_margin"
android:layout_marginBottom="@dimen/activity_stream_base_margin"
android:layout_marginRight="@dimen/activity_stream_base_margin"
android:layout_marginEnd="@dimen/activity_stream_base_margin"
- android:text="@string/activity_stream_highlights"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="#FF858585" />
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
@@ -29,16 +29,17 @@ public class ActivityStreamTelemetry {
public final static String ACTION_POSITION = "action_position";
public final static String COUNT = "count";
public final static String PAGE_NUMBER = "page_number";
public final static String INTERACTION = "interaction";
// Values
public final static String TYPE_TOPSITES = "topsites";
public final static String TYPE_HIGHLIGHTS = "highlights";
+ public final static String TYPE_POCKET = "pocket";
public final static String SUBTYPE_PINNED = "pinned";
public final static String SUBTYPE_SUGGESTED = "suggested";
public final static String SUBTYPE_TOP = "top";
public final static String SUBTYPE_VISITED = "visited";
public final static String SUBTYPE_BOOKMARKED = "bookmarked";
public final static String ITEM_SHARE = "share";
public final static String ITEM_ADD_BOOKMARK = "add_bookmark";
public final static String ITEM_REMOVE_BOOKMARK = "remove_bookmark";
@@ -138,16 +139,19 @@ public class ActivityStreamTelemetry {
public Builder forHighlightSource(Utils.HighlightSource source) {
switch (source) {
case VISITED:
this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_VISITED);
break;
case BOOKMARKED:
this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_BOOKMARKED);
break;
+ case POCKET:
+ this.set(Contract.SOURCE_TYPE, Contract.TYPE_POCKET);
+ break;
default:
throw new IllegalStateException("Unknown highlight source: " + source);
}
return this;
}
public Builder forTopSite(final TopSite topSite) {
this.set(
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/Utils.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/Utils.java
@@ -9,17 +9,18 @@ import android.database.Cursor;
import org.mozilla.gecko.activitystream.ranking.HighlightCandidateCursorIndices;
/**
* Various util methods and constants that are shared by different parts of Activity Stream.
*/
public class Utils {
public enum HighlightSource {
VISITED,
- BOOKMARKED
+ BOOKMARKED,
+ POCKET
}
public static HighlightSource highlightSource(final Cursor cursor, final HighlightCandidateCursorIndices cursorIndices) {
if (-1 != cursor.getLong(cursorIndices.bookmarkIDColumnIndex)) {
return HighlightSource.BOOKMARKED;
}
if (-1 != cursor.getLong(cursorIndices.historyIDColumnIndex)) {
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/HighlightsDividerItemDecoration.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/HighlightsDividerItemDecoration.java
@@ -12,17 +12,17 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* ItemDecoration implementation that draws horizontal divider line between highlight items.
*/
/* package */ class HighlightsDividerItemDecoration extends RecyclerView.ItemDecoration {
- // We do not want to draw a divider for the first items: Top sites panel and highlights title.
+ // We do not want to draw a divider for the Top Sites panel and the Welcome panel.
private static final int START_DRAWING_AT_POSITION = 2;
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
private Drawable divider;
/* package */ HighlightsDividerItemDecoration(Context context) {
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamHighlightItemRowContextMenuListener.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamHighlightItemRowContextMenuListener.java
@@ -1,18 +1,18 @@
/* 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.activitystream.homepanel;
import android.support.annotation.NonNull;
-import org.mozilla.gecko.activitystream.homepanel.stream.HighlightItemRow;
+import org.mozilla.gecko.activitystream.homepanel.stream.WebpageItemRow;
/**
* Provides a method to open the context menu for a highlight item.
*
* I tried declaring this inside StreamRecyclerAdapter but I got cyclical inheritance warnings
* (I don't understand why) so it's here instead.
*/
public interface StreamHighlightItemRowContextMenuListener {
- void openContextMenu(HighlightItemRow highlightItem, int position, @NonNull final String interactionExtra);
+ void openContextMenu(WebpageItemRow highlightItem, int position, @NonNull final String interactionExtra);
}
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamRecyclerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamRecyclerAdapter.java
@@ -8,61 +8,68 @@ package org.mozilla.gecko.activitystream
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
import org.mozilla.gecko.activitystream.homepanel.menu.ActivityStreamContextMenu;
import org.mozilla.gecko.activitystream.homepanel.model.RowModel;
+import org.mozilla.gecko.activitystream.homepanel.model.WebpageRowModel;
import org.mozilla.gecko.activitystream.homepanel.stream.TopPanelRow;
+import org.mozilla.gecko.activitystream.homepanel.model.TopStory;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
-import org.mozilla.gecko.activitystream.homepanel.stream.HighlightItemRow;
-import org.mozilla.gecko.activitystream.homepanel.stream.HighlightsTitleRow;
+import org.mozilla.gecko.activitystream.homepanel.stream.WebpageItemRow;
+import org.mozilla.gecko.activitystream.homepanel.stream.StreamTitleRow;
import org.mozilla.gecko.activitystream.homepanel.stream.StreamViewHolder;
import org.mozilla.gecko.activitystream.homepanel.stream.WelcomePanelRow;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
/**
* The adapter for the Activity Stream panel.
*
* Every item is in a single adapter: Top Sites, Welcome panel, Highlights.
*/
public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamViewHolder> implements RecyclerViewClickSupport.OnItemClickListener,
RecyclerViewClickSupport.OnItemLongClickListener, StreamHighlightItemRowContextMenuListener {
private static final String LOGTAG = StringUtils.safeSubstring("Gecko" + StreamRecyclerAdapter.class.getSimpleName(), 0, 23);
private Cursor topSitesCursor;
private List<RowModel> recyclerViewModel; // List of item types backing this RecyclerView.
+ private List<TopStory> topStoriesQueue;
- private final RowItemType[] FIXED_ROWS = {RowItemType.TOP_PANEL, RowItemType.WELCOME, RowItemType.HIGHLIGHTS_TITLE};
- private static final int HIGHLIGHTS_OFFSET = 3; // Topsites, Welcome, Highlights Title
+ private final RowItemType[] FIXED_ROWS = {RowItemType.TOP_PANEL, RowItemType.WELCOME, RowItemType.TOP_STORIES_TITLE, RowItemType.HIGHLIGHTS_TITLE};
+ private final int MAX_TOP_STORIES = 3;
private HomePager.OnUrlOpenListener onUrlOpenListener;
private HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
private int tiles;
private int tilesSize;
public enum RowItemType {
TOP_PANEL (-2), // RecyclerView.NO_ID is -1, so start hard-coded stableIds at -2.
WELCOME (-3),
- HIGHLIGHTS_TITLE (-4),
+ TOP_STORIES_TITLE(-4),
+ TOP_STORIES_ITEM(-1), // There can be multiple Top Stories items so caller should handle as a special case.
+ HIGHLIGHTS_TITLE (-5),
HIGHLIGHT_ITEM (-1); // There can be multiple Highlight Items so caller should handle as a special case.
public final int stableId;
RowItemType(int stableId) {
this.stableId = stableId;
}
@@ -81,16 +88,18 @@ public class StreamRecyclerAdapter exten
}
public StreamRecyclerAdapter() {
setHasStableIds(true);
recyclerViewModel = new LinkedList<>();
for (RowItemType type : FIXED_ROWS) {
recyclerViewModel.add(makeRowModelFromType(type));
}
+ topStoriesQueue = Collections.emptyList();
+ loadTopStories();
}
void setOnUrlOpenListeners(HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
this.onUrlOpenListener = onUrlOpenListener;
this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
}
public void setTileSize(int tiles, int tilesSize) {
@@ -109,81 +118,125 @@ public class StreamRecyclerAdapter exten
}
@Override
public StreamViewHolder onCreateViewHolder(ViewGroup parent, final int type) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (type == RowItemType.TOP_PANEL.getViewType()) {
return new TopPanelRow(inflater.inflate(TopPanelRow.LAYOUT_ID, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
+ } else if (type == RowItemType.TOP_STORIES_TITLE.getViewType()) {
+ return new StreamTitleRow(inflater.inflate(StreamTitleRow.LAYOUT_ID, parent, false), R.string.activity_stream_topstories);
+ } else if (type == RowItemType.TOP_STORIES_ITEM.getViewType()) {
+ return new WebpageItemRow(inflater.inflate(WebpageItemRow.LAYOUT_ID, parent, false), this);
} else if (type == RowItemType.WELCOME.getViewType()) {
return new WelcomePanelRow(inflater.inflate(WelcomePanelRow.LAYOUT_ID, parent, false), this);
} else if (type == RowItemType.HIGHLIGHT_ITEM.getViewType()) {
- return new HighlightItemRow(inflater.inflate(HighlightItemRow.LAYOUT_ID, parent, false), this);
+ return new WebpageItemRow(inflater.inflate(WebpageItemRow.LAYOUT_ID, parent, false), this);
} else if (type == RowItemType.HIGHLIGHTS_TITLE.getViewType()) {
- return new HighlightsTitleRow(inflater.inflate(HighlightsTitleRow.LAYOUT_ID, parent, false));
+ return new StreamTitleRow(inflater.inflate(StreamTitleRow.LAYOUT_ID, parent, false), R.string.activity_stream_highlights);
} else {
throw new IllegalStateException("Missing inflation for ViewType " + type);
}
}
- private int getHighlightsOffsetFromRVPosition(int position) {
- return position - HIGHLIGHTS_OFFSET;
+ /**
+ * Returns the index of an item within highlights.
+ * @param position position in adapter
+ * @return index of item within highlights
+ */
+ private int getHighlightsIndexFromAdapterPosition(int position) {
+ if (getItemViewType(position) != RowItemType.HIGHLIGHT_ITEM.getViewType()) {
+ throw new IllegalArgumentException("Item is not a highlight!");
+ }
+ return position - indexOfType(RowItemType.HIGHLIGHT_ITEM, recyclerViewModel);
+ }
+
+ /**
+ * Returns the index of an item within top stories.
+ * @param position position in adapter
+ * @return index of item within top stories
+ */
+ private int getTopStoriesIndexFromAdapterPosition(int position) {
+ if (getItemViewType(position) != RowItemType.TOP_STORIES_ITEM.getViewType()) {
+ throw new IllegalArgumentException("Item is not a topstory!");
+ }
+ return position - indexOfType(RowItemType.TOP_STORIES_ITEM, recyclerViewModel);
}
@Override
public void onBindViewHolder(StreamViewHolder holder, int position) {
int type = getItemViewType(position);
if (type == RowItemType.HIGHLIGHT_ITEM.getViewType()) {
final Highlight highlight = (Highlight) recyclerViewModel.get(position);
- ((HighlightItemRow) holder).bind(highlight, position, tilesSize);
+ ((WebpageItemRow) holder).bind(highlight, position, tilesSize);
} else if (type == RowItemType.TOP_PANEL.getViewType()) {
((TopPanelRow) holder).bind(topSitesCursor, tiles, tilesSize);
+ } else if (type == RowItemType.TOP_STORIES_ITEM.getViewType()) {
+ final TopStory story = (TopStory) recyclerViewModel.get(position);
+ ((WebpageItemRow) holder).bind(story, position, tilesSize);
}
}
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (!onItemClickIsValidHighlightItem(position)) {
+ if (!onItemClickIsValidRowItem(position)) {
return;
}
- final Highlight highlight = (Highlight) recyclerViewModel.get(position);
+ final WebpageRowModel model = (WebpageRowModel) recyclerViewModel.get(position);
+
+ final String sourceType;
+ final int actionPosition;
+ final int size;
+ final int viewType = getItemViewType(position);
+
+ if (viewType == RowItemType.HIGHLIGHT_ITEM.getViewType()) {
+ sourceType = ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS;
+ actionPosition = getHighlightsIndexFromAdapterPosition(position);
+ size = getNumOfTypeShown(RowItemType.HIGHLIGHT_ITEM);
+ } else {
+ sourceType = ActivityStreamTelemetry.Contract.TYPE_POCKET;
+ actionPosition = getTopStoriesIndexFromAdapterPosition(position);
+ size = getNumOfTypeShown(RowItemType.TOP_STORIES_ITEM);
+ }
ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
- .forHighlightSource(highlight.getSource())
- .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS)
- .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, getHighlightsOffsetFromRVPosition(position))
- .set(ActivityStreamTelemetry.Contract.COUNT, recyclerViewModel.size() - FIXED_ROWS.length);
+ .forHighlightSource(model.getSource())
+ .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, sourceType)
+ .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, actionPosition)
+ .set(ActivityStreamTelemetry.Contract.COUNT, size);
Telemetry.sendUIEvent(
TelemetryContract.Event.LOAD_URL,
TelemetryContract.Method.LIST_ITEM,
extras.build()
);
// NB: This is hacky. We need to process telemetry data first, otherwise we run a risk of
// not having a cursor to work with once url is opened and BrowserApp closes A-S home screen
// and clears its resources (read: cursors). See Bug 1326018.
- onUrlOpenListener.onUrlOpen(highlight.getUrl(), EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
+ onUrlOpenListener.onUrlOpen(model.getUrl(), EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
}
@Override
public boolean onItemLongClicked(final RecyclerView recyclerView, final int position, final View v) {
- if (!onItemClickIsValidHighlightItem(position)) {
+ if (!onItemClickIsValidRowItem(position)) {
return false;
}
- final HighlightItemRow highlightItem = (HighlightItemRow) recyclerView.getChildViewHolder(v);
+ final WebpageItemRow highlightItem = (WebpageItemRow) recyclerView.getChildViewHolder(v);
openContextMenu(highlightItem, position, ActivityStreamTelemetry.Contract.INTERACTION_LONG_CLICK);
return true;
}
- private boolean onItemClickIsValidHighlightItem(final int position) {
- if (getItemViewType(position) != RowItemType.HIGHLIGHT_ITEM.getViewType()) {
+ private boolean onItemClickIsValidRowItem(final int position) {
+ final int viewType = getItemViewType(position);
+ if (viewType != RowItemType.HIGHLIGHT_ITEM.getViewType()
+ && viewType != RowItemType.TOP_STORIES_ITEM.getViewType()) {
// Headers (containing topsites and/or the highlights title) do their own click handling as needed
return false;
}
// The position this method receives is from RecyclerView.ViewHolder.getAdapterPosition, whose docs state:
// "Note that if you've called notifyDataSetChanged(), until the next layout pass, the return value of this
// method will be NO_POSITION."
//
@@ -197,62 +250,136 @@ public class StreamRecyclerAdapter exten
Log.w(LOGTAG, "onItemClicked: received NO_POSITION. Returning");
return false;
}
return true;
}
@Override
- public void openContextMenu(final HighlightItemRow highlightItem, final int position, @NonNull final String interactionExtra) {
- final Highlight highlight = (Highlight) recyclerViewModel.get(position);
+ public void openContextMenu(final WebpageItemRow webpageItemRow, final int position, @NonNull final String interactionExtra) {
+ final WebpageRowModel model = (WebpageRowModel) recyclerViewModel.get(position);
+
+ final String sourceType;
+ final int actionPosition;
+ final ActivityStreamContextMenu.MenuMode menuMode;
+
+ if (model.getRowItemType() == RowItemType.HIGHLIGHT_ITEM) {
+ sourceType = ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS;
+ actionPosition = getHighlightsIndexFromAdapterPosition(position);
+ menuMode = ActivityStreamContextMenu.MenuMode.HIGHLIGHT;
+ } else {
+ sourceType = ActivityStreamTelemetry.Contract.TYPE_POCKET;
+ actionPosition = getTopStoriesIndexFromAdapterPosition(position);
+ menuMode = ActivityStreamContextMenu.MenuMode.TOPSTORY;
+ }
ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
- .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS)
- .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, position - HIGHLIGHTS_OFFSET)
+ .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, sourceType)
+ .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, actionPosition)
.set(ActivityStreamTelemetry.Contract.INTERACTION, interactionExtra)
- .forHighlightSource(highlight.getSource());
+ .forHighlightSource(model.getSource());
- ActivityStreamContextMenu.show(highlightItem.itemView.getContext(),
- highlightItem.getContextMenuAnchor(),
+ ActivityStreamContextMenu.show(webpageItemRow.itemView.getContext(),
+ webpageItemRow.getContextMenuAnchor(),
extras,
- ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
- highlight,
+ menuMode,
+ model,
/* shouldOverrideWithImageProvider */ true, // we use image providers in HighlightItem.pageIconLayout.
onUrlOpenListener, onUrlOpenInBackgroundListener,
- highlightItem.getTileWidth(), highlightItem.getTileHeight());
+ webpageItemRow.getTileWidth(), webpageItemRow.getTileHeight());
Telemetry.sendUIEvent(
TelemetryContract.Event.SHOW,
TelemetryContract.Method.CONTEXT_MENU,
extras.build()
);
}
@Override
public int getItemCount() {
return recyclerViewModel.size();
}
public void swapHighlights(List<Highlight> highlights) {
- recyclerViewModel = recyclerViewModel.subList(0, HIGHLIGHTS_OFFSET);
+ recyclerViewModel = recyclerViewModel.subList(0, FIXED_ROWS.length + getNumOfTypeShown(RowItemType.TOP_STORIES_ITEM));
recyclerViewModel.addAll(highlights);
notifyDataSetChanged();
}
+ private void loadTopStories() {
+ List<TopStory> newStories = makePlaceholderStories();
+ topStoriesQueue = newStories;
+
+ final int insertionIndex = indexOfType(RowItemType.TOP_STORIES_TITLE, recyclerViewModel) + 1;
+ for (int i = 0; i < Math.min(MAX_TOP_STORIES, newStories.size()); i++) {
+ recyclerViewModel.add(insertionIndex + i, newStories.get(i));
+ }
+ }
+
+ /**
+ * Returns the index of the first item of the type found.
+ * @param type viewType of RowItemType
+ * @param rowModelList List to be indexed into
+ * @return index of first item of the type, or -1 if it none exist.
+ */
+ private static int indexOfType(RowItemType type, List<RowModel> rowModelList) {
+ for (int i = 0; i < rowModelList.size(); i++) {
+ if (rowModelList.get(i).getRowItemType() == type) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the number of consecutive items in the adapter of the item type specified.
+ *
+ * This is intended to be used for counting the items that have a dynamic count
+ * (such as Highlights or TopStory)
+ *
+ * @param type RowItemType to be counted
+ * @return The number of items shown.
+ */
+ private int getNumOfTypeShown(RowItemType type) {
+ final int startIndex = indexOfType(type, recyclerViewModel);
+ if (startIndex == -1) {
+ return 0;
+ }
+ int count = 0;
+ for (int i = startIndex; i < recyclerViewModel.size(); i++) {
+ if (getItemViewType(i) == type.getViewType()) {
+ count++;
+ } else {
+ break;
+ }
+ }
+ return count;
+ }
+
+ private List<TopStory> makePlaceholderStories() {
+ final List<TopStory> stories = new LinkedList<>();
+ final String[] TITLES = { "Placeholder 1", "Placeholder 2", "Placeholder 3"};
+ for (String title : TITLES) {
+ stories.add(new TopStory(title, "https://www.mozilla.org/"));
+ }
+ return stories;
+ }
+
public void swapTopSitesCursor(Cursor cursor) {
this.topSitesCursor = cursor;
notifyItemChanged(0);
}
@Override
public long getItemId(int position) {
final int viewType = getItemViewType(position);
- if (viewType == RowItemType.HIGHLIGHT_ITEM.getViewType()) {
+ if (viewType == RowItemType.HIGHLIGHT_ITEM.getViewType()
+ || viewType == RowItemType.TOP_STORIES_ITEM.getViewType()) {
// Highlights are always picked from recent history - So using the history id should
// give us a unique (positive) id.
- final Highlight highlight = (Highlight) recyclerViewModel.get(position);
- return highlight.getHistoryId();
+ final WebpageRowModel model = (WebpageRowModel) recyclerViewModel.get(position);
+ return model.getUniqueId();
} else {
return recyclerViewModel.get(position).getRowItemType().stableId;
}
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/ActivityStreamContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/ActivityStreamContextMenu.java
@@ -30,17 +30,18 @@ import org.mozilla.gecko.util.UIAsyncTas
import java.util.EnumSet;
@RobocopTarget
public abstract class ActivityStreamContextMenu
implements NavigationView.OnNavigationItemSelectedListener {
public enum MenuMode {
HIGHLIGHT,
- TOPSITE
+ TOPSITE,
+ TOPSTORY
}
private final Context context;
private final WebpageModel item;
private final ActivityStreamTelemetry.Extras.Builder telemetryExtraBuilder;
private final HomePager.OnUrlOpenListener onUrlOpenListener;
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/Highlight.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/Highlight.java
@@ -13,17 +13,17 @@ import android.text.TextUtils;
import org.mozilla.gecko.activitystream.Utils;
import org.mozilla.gecko.activitystream.homepanel.StreamRecyclerAdapter;
import org.mozilla.gecko.activitystream.ranking.HighlightCandidateCursorIndices;
import org.mozilla.gecko.activitystream.ranking.HighlightsRanking;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class Highlight implements WebpageModel, RowModel {
+public class Highlight implements WebpageRowModel {
/**
* A pattern matching a json object containing the key "image_url" and extracting the value. afaik, these urls
* are not encoded so it's entirely possible that the url will contain a quote and we will not extract the whole
* url. However, given these are coming from websites providing favicon-like images, it's not likely a quote will
* appear and since these urls are only being used to compare against one another (as imageURLs in Highlight items),
* a partial URL may actually have the same behavior: good enough for me!
*/
@@ -209,16 +209,18 @@ public class Highlight implements Webpag
this.isBookmarked = bookmarked;
}
@Override
public void updatePinned(boolean pinned) {
this.isPinned = pinned;
}
+ @Override
public Utils.HighlightSource getSource() {
return source;
}
- public long getHistoryId() {
+ @Override
+ public long getUniqueId() {
return historyId;
}
}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/TopStory.java
@@ -0,0 +1,68 @@
+/* -*- 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.activitystream.homepanel.model;
+
+import org.mozilla.gecko.activitystream.Utils;
+import org.mozilla.gecko.activitystream.homepanel.StreamRecyclerAdapter;
+
+public class TopStory implements WebpageRowModel {
+ private final String title;
+ private final String url;
+ private final String imageUrl;
+
+ public TopStory(String title, String url) {
+ this(title, url, null);
+ }
+
+ public TopStory(String title, String url, String imageUrl) {
+ this.title = title;
+ this.url = url;
+ this.imageUrl = imageUrl;
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public String getUrl() {
+ return url;
+ }
+
+ @Override
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ @Override
+ public StreamRecyclerAdapter.RowItemType getRowItemType() {
+ return StreamRecyclerAdapter.RowItemType.TOP_STORIES_ITEM;
+ }
+
+ @Override
+ public Boolean isBookmarked() {
+ return false;
+ }
+
+ @Override
+ public Boolean isPinned() {
+ return false;
+ }
+
+ public void updateBookmarked(boolean bookmarked) {}
+ public void updatePinned(boolean pinned) {}
+
+ @Override
+ public Utils.HighlightSource getSource() {
+ return Utils.HighlightSource.POCKET;
+ }
+
+ @Override
+ public long getUniqueId() {
+ return getUrl().hashCode();
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/WebpageRowModel.java
@@ -0,0 +1,16 @@
+/* -*- 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.activitystream.homepanel.model;
+
+import org.mozilla.gecko.activitystream.Utils;
+
+/**
+ * Model for a row in Activity Stream that represents a webpage item.
+ */
+public interface WebpageRowModel extends WebpageModel, RowModel {
+ Utils.HighlightSource getSource();
+ long getUniqueId();
+}
rename from mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/HighlightsTitleRow.java
rename to mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/StreamTitleRow.java
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/HighlightsTitleRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/StreamTitleRow.java
@@ -1,19 +1,24 @@
/* -*- 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.activitystream.homepanel.stream;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
import android.view.View;
+import android.widget.TextView;
import org.mozilla.gecko.R;
-public class HighlightsTitleRow extends StreamViewHolder {
+public class StreamTitleRow extends StreamViewHolder {
public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
- public HighlightsTitleRow(final View itemView) {
+ public StreamTitleRow(final View itemView, final @StringRes @NonNull int titleResId) {
super(itemView);
+ final TextView titleView = (TextView) itemView.findViewById(R.id.title_highlights);
+ titleView.setText(titleResId);
}
}
rename from mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/HighlightItemRow.java
rename to mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/WebpageItemRow.java
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/HighlightItemRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/stream/WebpageItemRow.java
@@ -12,108 +12,115 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
import org.mozilla.gecko.activitystream.Utils;
import org.mozilla.gecko.activitystream.homepanel.StreamHighlightItemRowContextMenuListener;
-import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
+import org.mozilla.gecko.activitystream.homepanel.model.WebpageRowModel;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.TouchTargetUtil;
import org.mozilla.gecko.util.URIUtils;
import org.mozilla.gecko.util.ViewUtil;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
-public class HighlightItemRow extends StreamViewHolder {
- private static final String LOGTAG = "GeckoHighlightItem";
+public class WebpageItemRow extends StreamViewHolder {
+ private static final String LOGTAG = "GeckoWebpageItemRow";
public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
private static final double SIZE_RATIO = 0.75;
+ private WebpageRowModel webpageModel;
private int position;
private final StreamOverridablePageIconLayout pageIconLayout;
+ private final TextView pageDomainView;
private final TextView pageTitleView;
+ private final ImageView pageSourceIconView;
private final TextView pageSourceView;
- private final TextView pageDomainView;
- private final ImageView pageSourceIconView;
private final ImageView menuButton;
- public HighlightItemRow(final View itemView, final StreamHighlightItemRowContextMenuListener contextMenuListener) {
+ public WebpageItemRow(final View itemView, final StreamHighlightItemRowContextMenuListener contextMenuListener) {
super(itemView);
pageTitleView = (TextView) itemView.findViewById(R.id.card_history_label);
pageIconLayout = (StreamOverridablePageIconLayout) itemView.findViewById(R.id.icon);
pageSourceView = (TextView) itemView.findViewById(R.id.card_history_source);
pageDomainView = (TextView) itemView.findViewById(R.id.page);
pageSourceIconView = (ImageView) itemView.findViewById(R.id.source_icon);
menuButton = (ImageView) itemView.findViewById(R.id.menu);
menuButton.setImageDrawable(
DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
TouchTargetUtil.ensureTargetHitArea(menuButton, itemView);
ViewUtil.enableTouchRipple(menuButton);
menuButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- contextMenuListener.openContextMenu(HighlightItemRow.this, position,
+ contextMenuListener.openContextMenu(WebpageItemRow.this, position,
ActivityStreamTelemetry.Contract.INTERACTION_MENU_BUTTON);
}
});
}
- public void bind(Highlight highlight, int position, int tilesWidth) {
+ public void bind(WebpageRowModel model, int position, int tilesWidth) {
+ this.webpageModel = model;
this.position = position;
- final String backendHightlightTitle = highlight.getTitle();
- final String uiHighlightTitle = !TextUtils.isEmpty(backendHightlightTitle) ? backendHightlightTitle : highlight.getUrl();
+ final String backendHighlightTitle = model.getTitle();
+ final String uiHighlightTitle = !TextUtils.isEmpty(backendHighlightTitle) ? backendHighlightTitle : model.getUrl();
pageTitleView.setText(uiHighlightTitle);
ViewGroup.LayoutParams layoutParams = pageIconLayout.getLayoutParams();
layoutParams.width = tilesWidth;
layoutParams.height = (int) Math.floor(tilesWidth * SIZE_RATIO);
pageIconLayout.setLayoutParams(layoutParams);
- updateUiForSource(highlight.getSource());
- updatePageDomain(highlight);
- pageIconLayout.updateIcon(highlight.getUrl(), highlight.getImageUrl());
+ updateUiForSource(model.getSource());
+ updatePageDomain();
+ pageIconLayout.updateIcon(model.getUrl(), model.getImageUrl());
}
- private void updateUiForSource(Utils.HighlightSource source) {
+ public void updateUiForSource(Utils.HighlightSource source) {
switch (source) {
case BOOKMARKED:
pageSourceView.setText(R.string.activity_stream_highlight_label_bookmarked);
pageSourceView.setVisibility(View.VISIBLE);
pageSourceIconView.setImageResource(R.drawable.ic_as_bookmarked);
break;
case VISITED:
pageSourceView.setText(R.string.activity_stream_highlight_label_visited);
pageSourceView.setVisibility(View.VISIBLE);
pageSourceIconView.setImageResource(R.drawable.ic_as_visited);
break;
+ case POCKET:
+ pageSourceView.setText(R.string.activity_stream_highlight_label_trending);
+ pageSourceView.setVisibility(View.VISIBLE);
+ pageSourceIconView.setImageResource(R.drawable.ic_as_trending);
+ break;
default:
pageSourceView.setVisibility(View.INVISIBLE);
pageSourceIconView.setImageResource(0);
break;
}
}
- private void updatePageDomain(final Highlight highlight) {
+ private void updatePageDomain() {
final URI highlightURI;
try {
- highlightURI = new URI(highlight.getUrl());
+ highlightURI = new URI(webpageModel.getUrl());
} catch (final URISyntaxException e) {
// If the URL is invalid, there's not much extra processing we can do on it.
- pageDomainView.setText(highlight.getUrl());
+ pageDomainView.setText(webpageModel.getUrl());
return;
}
final UpdatePageDomainAsyncTask updatePageDomainTask = new UpdatePageDomainAsyncTask(itemView.getContext(),
highlightURI, pageDomainView);
updatePageDomainTask.execute();
}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -829,24 +829,28 @@ just addresses the organization to follo
<!ENTITY helper_first_offline_bookmark_message "Find your Reader View items in Bookmarks, even offline.">
<!ENTITY helper_first_offline_bookmark_button "Go to Bookmarks">
<!ENTITY helper_triple_readerview_open_title "Available offline">
<!ENTITY helper_triple_readerview_open_message "Bookmark Reader View items to read them offline.">
<!ENTITY helper_triple_readerview_open_button "Add to Bookmarks">
<!ENTITY activity_stream_topsites "Top Sites">
+<!-- LOCALIZATION NOTE (activity_stream_topstories): &brandPocket is the brand of the company, Pocket, that is being used to provide suggestions for articles. -->
+<!ENTITY activity_stream_topstories "Recommended by &brandPocket;">
<!ENTITY activity_stream_highlights "Highlights">
<!-- LOCALIZATION NOTE (activity_stream_highlight_label_bookmarked): This label is shown in the Activity
Stream list for highlights sourced from th user's bookmarks. -->
<!ENTITY activity_stream_highlight_label_bookmarked "Bookmarked">
<!-- LOCALIZATION NOTE (activity_stream_highlight_label_visited): This label is shown in the Activity
Stream list for highlights sourced from th user's bookmarks. -->
<!ENTITY activity_stream_highlight_label_visited "Visited">
+<!-- LOCALIZATION NOTE (activity_stream_highlight_label_trending): This label is shown in the Activity Stream list for highlights sourced from a recommendations engine. -->
+<!ENTITY activity_stream_highlight_label_trending "Trending">
<!-- LOCALIZATION NOTE (activity_stream_remove): This label is shown in the Activity Stream context menu,
and allows hiding a URL/page from highlights or topsites. The page remains in history/bookmarks, but
is simply hidden from the Activity Stream panel. -->
<!ENTITY activity_stream_remove "Remove">
<!ENTITY activity_stream_delete_history "Delete from History">
<!ENTITY activity_stream_welcome_title "Welcome to your Highlights">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -511,22 +511,24 @@ gbjar.sources += ['java/org/mozilla/geck
'activitystream/homepanel/HighlightsLoader.java',
'activitystream/homepanel/menu/ActivityStreamContextMenu.java',
'activitystream/homepanel/menu/BottomSheetContextMenu.java',
'activitystream/homepanel/menu/PopupContextMenu.java',
'activitystream/homepanel/model/Highlight.java',
'activitystream/homepanel/model/Metadata.java',
'activitystream/homepanel/model/RowModel.java',
'activitystream/homepanel/model/TopSite.java',
+ 'activitystream/homepanel/model/TopStory.java',
'activitystream/homepanel/model/WebpageModel.java',
- 'activitystream/homepanel/stream/HighlightItemRow.java',
- 'activitystream/homepanel/stream/HighlightsTitleRow.java',
+ 'activitystream/homepanel/model/WebpageRowModel.java',
'activitystream/homepanel/stream/StreamOverridablePageIconLayout.java',
+ 'activitystream/homepanel/stream/StreamTitleRow.java',
'activitystream/homepanel/stream/StreamViewHolder.java',
'activitystream/homepanel/stream/TopPanelRow.java',
+ 'activitystream/homepanel/stream/WebpageItemRow.java',
'activitystream/homepanel/stream/WelcomePanelRow.java',
'activitystream/homepanel/StreamHighlightItemRowContextMenuListener.java',
'activitystream/homepanel/StreamItemAnimator.java',
'activitystream/homepanel/StreamRecyclerAdapter.java',
'activitystream/homepanel/topsites/TopSitesCard.java',
'activitystream/homepanel/topsites/TopSitesPage.java',
'activitystream/homepanel/topsites/TopSitesPageAdapter.java',
'activitystream/homepanel/topsites/TopSitesPagerAdapter.java',
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -617,19 +617,21 @@
<string name="helper_first_offline_bookmark_message">&helper_first_offline_bookmark_message;</string>
<string name="helper_first_offline_bookmark_button">&helper_first_offline_bookmark_button;</string>
<string name="helper_triple_readerview_open_title">&helper_triple_readerview_open_title;</string>
<string name="helper_triple_readerview_open_message">&helper_triple_readerview_open_message;</string>
<string name="helper_triple_readerview_open_button">&helper_triple_readerview_open_button;</string>
<string name="activity_stream_topsites">&activity_stream_topsites;</string>
+ <string name="activity_stream_topstories">&activity_stream_topstories;</string>
<string name="activity_stream_highlights">&activity_stream_highlights;</string>
<string name="activity_stream_highlight_label_bookmarked">&activity_stream_highlight_label_bookmarked;</string>
<string name="activity_stream_highlight_label_visited">&activity_stream_highlight_label_visited;</string>
+ <string name="activity_stream_highlight_label_trending">&activity_stream_highlight_label_trending;</string>
<string name="activity_stream_remove">&activity_stream_remove;</string>
<string name="activity_stream_delete_history">&activity_stream_delete_history;</string>
<string name="activity_stream_welcome_title">&activity_stream_welcome_title;</string>
<string name="activity_stream_welcome_content">&activity_stream_welcome_content1;</string>
<string name="activity_stream_welcome_dismiss">&activity_stream_welcome_dismiss;</string>
<string name="private_tab_panel_title">&private_tab_panel_title;</string>
--- a/mobile/android/branding/beta/locales/en-US/brand.dtd
+++ b/mobile/android/branding/beta/locales/en-US/brand.dtd
@@ -1,7 +1,9 @@
<!-- 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/. -->
<!ENTITY brandShortName "Firefox Beta">
<!ENTITY brandFullName "Mozilla Firefox Beta">
-<!ENTITY vendorShortName "Mozilla">
\ No newline at end of file
+<!ENTITY vendorShortName "Mozilla">
+
+<!ENTITY brandPocket "Pocket">
--- a/mobile/android/branding/nightly-old-id/locales/en-US/brand.dtd
+++ b/mobile/android/branding/nightly-old-id/locales/en-US/brand.dtd
@@ -1,7 +1,9 @@
<!-- 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/. -->
<!ENTITY brandShortName "Nightly">
<!ENTITY brandFullName "Mozilla Nightly">
-<!ENTITY vendorShortName "Mozilla">
\ No newline at end of file
+<!ENTITY vendorShortName "Mozilla">
+
+<!ENTITY brandPocket "Pocket">
--- a/mobile/android/branding/nightly/locales/en-US/brand.dtd
+++ b/mobile/android/branding/nightly/locales/en-US/brand.dtd
@@ -1,7 +1,9 @@
<!-- 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/. -->
<!ENTITY brandShortName "Nightly">
<!ENTITY brandFullName "Mozilla Nightly">
-<!ENTITY vendorShortName "Mozilla">
\ No newline at end of file
+<!ENTITY vendorShortName "Mozilla">
+
+<!ENTITY brandPocket "Pocket">
--- a/mobile/android/branding/official/locales/en-US/brand.dtd
+++ b/mobile/android/branding/official/locales/en-US/brand.dtd
@@ -1,7 +1,9 @@
<!-- 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/. -->
<!ENTITY brandShortName "Firefox">
<!ENTITY brandFullName "Mozilla Firefox">
-<!ENTITY vendorShortName "Mozilla">
\ No newline at end of file
+<!ENTITY vendorShortName "Mozilla">
+
+<!ENTITY brandPocket "Pocket">
--- a/mobile/android/branding/unofficial/locales/en-US/brand.dtd
+++ b/mobile/android/branding/unofficial/locales/en-US/brand.dtd
@@ -1,7 +1,9 @@
<!-- 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/. -->
<!ENTITY brandShortName "Fennec">
<!ENTITY brandFullName "Mozilla Fennec">
-<!ENTITY vendorShortName "Mozilla">
\ No newline at end of file
+<!ENTITY vendorShortName "Mozilla">
+
+<!ENTITY brandPocket "Pocket">