Bug 1312477 - Pre: refactor AS context menu to allow multiple view implementations r?sebastian
This is in preparation for having a popup menu on tablets.
MozReview-Commit-ID: 14thIuhRkgB
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
@@ -104,17 +104,19 @@ public abstract class StreamItem extends
menuButton.setImageDrawable(
DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
TouchTargetUtil.ensureTargetHitArea(menuButton, itemView);
menuButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- ActivityStreamContextMenu.show(v.getContext(), ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
+ ActivityStreamContextMenu.show(v.getContext(),
+ menuButton,
+ ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
title, url, onUrlOpenListener, onUrlOpenInBackgroundListener,
vIconView.getWidth(), vIconView.getHeight());
}
});
}
public void bind(Cursor cursor, int tilesWidth, int tilesHeight) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
@@ -3,122 +3,93 @@
* 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.menu;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.support.annotation.NonNull;
-import android.support.design.widget.BottomSheetBehavior;
-import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.NavigationView;
-import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.IntentHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.activitystream.ActivityStream;
+import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UIAsyncTask;
-import org.mozilla.gecko.widget.FaviconView;
import java.util.EnumSet;
-import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
-
-public class ActivityStreamContextMenu
- extends BottomSheetDialog
+@RobocopTarget
+public abstract class ActivityStreamContextMenu
implements NavigationView.OnNavigationItemSelectedListener {
public enum MenuMode {
HIGHLIGHT,
TOPSITE
}
final Context context;
final String title;
final String url;
final HomePager.OnUrlOpenListener onUrlOpenListener;
final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
- boolean isAlreadyBookmarked = false;
+ boolean isAlreadyBookmarked; // default false;
+
+ public abstract MenuItem getItemByID(int id);
+
+ public abstract void show();
+
+ public abstract void dismiss();
- private ActivityStreamContextMenu(final Context context,
- final MenuMode mode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
- final int tilesWidth, final int tilesHeight) {
- super(context);
+ final MenuMode mode;
+ /* package-private */ ActivityStreamContextMenu(final Context context,
+ final MenuMode mode,
+ final String title, @NonNull final String url,
+ HomePager.OnUrlOpenListener onUrlOpenListener,
+ HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
this.context = context;
+ this.mode = mode;
+
this.title = title;
this.url = url;
this.onUrlOpenListener = onUrlOpenListener;
this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
-
- final LayoutInflater inflater = LayoutInflater.from(context);
-
- final View content = inflater.inflate(R.layout.activity_stream_contextmenu_layout, null);
- setContentView(content);
-
- ((TextView) findViewById(R.id.title)).setText(title);
- extractLabel(context, url, false, new ActivityStream.LabelCallback() {
- public void onLabelExtracted(String label) {
- ((TextView) findViewById(R.id.url)).setText(label);
- }
- });
-
- // Copy layouted parameters from the Highlights / TopSites items to ensure consistency
- final FaviconView faviconView = (FaviconView) findViewById(R.id.icon);
- ViewGroup.LayoutParams layoutParams = faviconView.getLayoutParams();
- layoutParams.width = tilesWidth;
- layoutParams.height = tilesHeight;
- faviconView.setLayoutParams(layoutParams);
-
- Icons.with(context)
- .pageUrl(url)
- .skipNetwork()
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- faviconView.updateImage(response);
- }
- });
-
- NavigationView navigationView = (NavigationView) findViewById(R.id.menu);
- navigationView.setNavigationItemSelectedListener(this);
-
+ }
+
+ /**
+ * Must be called before the menu is shown.
+ * <p/>
+ * Your implementation must be ready to return items from getItemByID() before postInit() is
+ * called, i.e. you should probably inflate your menu items before this call.
+ */
+ protected void postInit() {
// Disable "dismiss" for topsites until we have decided on its behaviour for topsites
// (currently "dismiss" adds the URL to a highlights-specific blocklist, which the topsites
// query has no knowledge of).
if (mode == MenuMode.TOPSITE) {
- final MenuItem dismissItem = navigationView.getMenu().findItem(R.id.dismiss);
+ final MenuItem dismissItem = getItemByID(R.id.dismiss);
dismissItem.setVisible(false);
}
// Disable the bookmark item until we know its bookmark state
- final MenuItem bookmarkItem = navigationView.getMenu().findItem(R.id.bookmark);
+ final MenuItem bookmarkItem = getItemByID(R.id.bookmark);
bookmarkItem.setEnabled(false);
(new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
@Override
protected Void doInBackground() {
isAlreadyBookmarked = BrowserDB.from(context).isBookmark(context.getContentResolver(), url);
return null;
}
@@ -129,20 +100,19 @@ public class ActivityStreamContextMenu
bookmarkItem.setTitle(R.string.bookmark_remove);
}
bookmarkItem.setEnabled(true);
}
}).execute();
// Only show the "remove from history" item if a page actually has history
- final MenuItem deleteHistoryItem = navigationView.getMenu().findItem(R.id.delete);
+ final MenuItem deleteHistoryItem = getItemByID(R.id.delete);
deleteHistoryItem.setVisible(false);
-
(new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
boolean hasHistory;
@Override
protected Void doInBackground() {
final Cursor cursor = BrowserDB.from(context).getHistoryForURL(context.getContentResolver(), url);
try {
if (cursor != null &&
@@ -159,36 +129,18 @@ public class ActivityStreamContextMenu
@Override
protected void onPostExecute(Void aVoid) {
if (hasHistory) {
deleteHistoryItem.setVisible(true);
}
}
}).execute();
-
- BottomSheetBehavior<View> bsBehaviour = BottomSheetBehavior.from((View) content.getParent());
- bsBehaviour.setPeekHeight(context.getResources().getDimensionPixelSize(R.dimen.activity_stream_contextmenu_peek_height));
}
- public static ActivityStreamContextMenu show(Context context,
- final MenuMode menuMode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
- final int tilesWidth, final int tilesHeight) {
- final ActivityStreamContextMenu menu = new ActivityStreamContextMenu(context,
- menuMode,
- title, url,
- onUrlOpenListener, onUrlOpenInBackgroundListener,
- tilesWidth, tilesHeight);
- menu.show();
-
- return menu;
- }
@Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share:
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");
IntentHelper.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title, false);
break;
@@ -248,9 +200,30 @@ public class ActivityStreamContextMenu
default:
throw new IllegalArgumentException("Menu item with ID=" + item.getItemId() + " not handled");
}
dismiss();
return true;
}
+
+
+ @RobocopTarget
+ public static ActivityStreamContextMenu show(Context context,
+ View anchor,
+ final MenuMode menuMode,
+ final String title, @NonNull final String url,
+ HomePager.OnUrlOpenListener onUrlOpenListener,
+ HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
+ final int tilesWidth, final int tilesHeight) {
+ final ActivityStreamContextMenu menu;
+
+ menu = new BottomSheetContextMenu(context,
+ menuMode,
+ title, url,
+ onUrlOpenListener, onUrlOpenInBackgroundListener,
+ tilesWidth, tilesHeight);
+
+ menu.show();
+ return menu;
+ }
}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java
@@ -0,0 +1,105 @@
+/* -*- 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.menu;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.design.widget.BottomSheetBehavior;
+import android.support.design.widget.BottomSheetDialog;
+import android.support.design.widget.NavigationView;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.activitystream.ActivityStream;
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.icons.IconCallback;
+import org.mozilla.gecko.icons.IconResponse;
+import org.mozilla.gecko.icons.Icons;
+import org.mozilla.gecko.widget.FaviconView;
+
+import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
+
+/* package-private */ class BottomSheetContextMenu
+ extends ActivityStreamContextMenu {
+
+
+ private final BottomSheetDialog bottomSheetDialog;
+
+ private final NavigationView navigationView;
+
+ public BottomSheetContextMenu(final Context context,
+ final MenuMode mode,
+ final String title, @NonNull final String url,
+ HomePager.OnUrlOpenListener onUrlOpenListener,
+ HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
+ final int tilesWidth, final int tilesHeight) {
+
+ super(context,
+ mode,
+ title,
+ url,
+ onUrlOpenListener,
+ onUrlOpenInBackgroundListener);
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final View content = inflater.inflate(R.layout.activity_stream_contextmenu_bottomsheet, null);
+
+ bottomSheetDialog = new BottomSheetDialog(context);
+ bottomSheetDialog.setContentView(content);
+
+ ((TextView) content.findViewById(R.id.title)).setText(title);
+
+ extractLabel(context, url, false, new ActivityStream.LabelCallback() {
+ public void onLabelExtracted(String label) {
+ ((TextView) content.findViewById(R.id.url)).setText(label);
+ }
+ });
+
+ // Copy layouted parameters from the Highlights / TopSites items to ensure consistency
+ final FaviconView faviconView = (FaviconView) content.findViewById(R.id.icon);
+ ViewGroup.LayoutParams layoutParams = faviconView.getLayoutParams();
+ layoutParams.width = tilesWidth;
+ layoutParams.height = tilesHeight;
+ faviconView.setLayoutParams(layoutParams);
+
+ Icons.with(context)
+ .pageUrl(url)
+ .skipNetwork()
+ .build()
+ .execute(new IconCallback() {
+ @Override
+ public void onIconResponse(IconResponse response) {
+ faviconView.updateImage(response);
+ }
+ });
+
+ navigationView = (NavigationView) content.findViewById(R.id.menu);
+ navigationView.setNavigationItemSelectedListener(this);
+
+ BottomSheetBehavior<View> bsBehaviour = BottomSheetBehavior.from((View) content.getParent());
+ bsBehaviour.setPeekHeight(context.getResources().getDimensionPixelSize(R.dimen.activity_stream_contextmenu_peek_height));
+
+ super.postInit();
+ }
+
+ @Override
+ public MenuItem getItemByID(int id) {
+ return navigationView.getMenu().findItem(id);
+ }
+
+ @Override
+ public void show() {
+ bottomSheetDialog.show();
+ }
+
+ public void dismiss() {
+ bottomSheetDialog.dismiss();
+ }
+
+}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
@@ -87,15 +87,16 @@ class TopSitesCard extends RecyclerView.
}
@Override
public void onClick(View clickedView) {
if (clickedView == itemView) {
onUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
} else if (clickedView == menuButton) {
ActivityStreamContextMenu.show(clickedView.getContext(),
+ menuButton,
ActivityStreamContextMenu.MenuMode.TOPSITE,
title.getText().toString(), url,
onUrlOpenListener, onUrlOpenInBackgroundListener,
faviconView.getWidth(), faviconView.getHeight());
}
}
}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -434,16 +434,17 @@ gbjar.sources += ['java/org/mozilla/geck
'GuestSession.java',
'health/HealthRecorder.java',
'health/SessionInformation.java',
'health/StubbedHealthRecorder.java',
'home/activitystream/ActivityStream.java',
'home/activitystream/ActivityStreamHomeFragment.java',
'home/activitystream/ActivityStreamHomeScreen.java',
'home/activitystream/menu/ActivityStreamContextMenu.java',
+ 'home/activitystream/menu/BottomSheetContextMenu.java',
'home/activitystream/StreamItem.java',
'home/activitystream/StreamRecyclerAdapter.java',
'home/activitystream/topsites/CirclePageIndicator.java',
'home/activitystream/topsites/TopSitesCard.java',
'home/activitystream/topsites/TopSitesPage.java',
'home/activitystream/topsites/TopSitesPageAdapter.java',
'home/activitystream/topsites/TopSitesPagerAdapter.java',
'home/BookmarkFolderView.java',
rename from mobile/android/base/resources/layout/activity_stream_contextmenu_layout.xml
rename to mobile/android/base/resources/layout/activity_stream_contextmenu_bottomsheet.xml
--- a/mobile/android/base/resources/layout/activity_stream_contextmenu_layout.xml
+++ b/mobile/android/base/resources/layout/activity_stream_contextmenu_bottomsheet.xml
@@ -58,19 +58,21 @@
android:background="@color/disabled_grey"
android:padding="4dp"/>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="@dimen/activity_stream_contextmenu_max_menu_height">
<android.support.design.widget.NavigationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ app:itemTextAppearance="@style/ActivityStreamContextMenuText"
android:theme="@style/ActivityStreamContextMenuStyle"
- app:itemTextAppearance="@style/ActivityStreamContextMenuText"
app:menu="@menu/activitystream_contextmenu"/>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testActivityStreamContextMenu.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testActivityStreamContextMenu.java
@@ -43,52 +43,36 @@ public class testActivityStreamContextMe
testMenuForUrl(testURL, false, true);
}
/**
* Test that the menu shows the expected menu items for a given URL, and that these items have
* the correct state.
*/
private void testMenuForUrl(final String url, final boolean isBookmarked, final boolean isVisited) {
- final ActivityStreamContextMenu menu = ActivityStreamContextMenu.show(getActivity(), ActivityStreamContextMenu.MenuMode.HIGHLIGHT, "foobar", url, null, null, 100, 100);
-
- waitForContextMenu(menu);
+ final View anchor = new View(getActivity());
- final View wrapper = menu.findViewById(R.id.info_wrapper);
- mAsserter.is(wrapper.getVisibility(), View.VISIBLE, "menu should be visible");
-
- NavigationView nv = (NavigationView) menu.findViewById(R.id.menu);
+ final ActivityStreamContextMenu menu = ActivityStreamContextMenu.show(getActivity(), anchor, ActivityStreamContextMenu.MenuMode.HIGHLIGHT, "foobar", url, null, null, 100, 100);
final int expectedBookmarkString;
if (isBookmarked) {
expectedBookmarkString = R.string.bookmark_remove;
} else {
expectedBookmarkString = R.string.bookmark;
}
- final MenuItem bookmarkItem = nv.getMenu().findItem(R.id.bookmark);
+ final MenuItem bookmarkItem = menu.getItemByID(R.id.bookmark);
assertMenuItemHasString(bookmarkItem, expectedBookmarkString);
- final MenuItem deleteItem = nv.getMenu().findItem(R.id.delete);
+ final MenuItem deleteItem = menu.getItemByID(R.id.delete);
assertMenuItemIsVisible(deleteItem, isVisited);
menu.dismiss();
}
- private void waitForContextMenu(final ActivityStreamContextMenu menu) {
- waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- final View v = menu.findViewById(R.id.info_wrapper);
-
- return (v != null);
- }
- }, 5000);
- }
-
private void assertMenuItemIsVisible(final MenuItem item, final boolean shouldBeVisible) {
waitForCondition(new Condition() {
@Override
public boolean isSatisfied() {
return (item.isVisible() == shouldBeVisible);
}
}, 5000);