Bug 1393672 - Add a badge (at the same position of Page Actio) for PWA. r?jwu
This bug is for the front end work to make users more engaged with PWA.
Requirements
1. When the user goes to a web page that has manifest.json, we show a badge(in the same position of page action).
2. If the user switch to another normal page or other Java UI, the page action should be gone.
3. When the user see the PWA website for the first time, we will display a onboarding prompt to teach him how to add PWA to home screen. This prompt only shows once.
MozReview-Commit-ID: AcyjHPVKg2b
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -491,16 +491,23 @@ public class GeckoApplication extends Ap
public LightweightTheme getLightweightTheme() {
return mLightweightTheme;
}
public void prepareLightweightTheme() {
mLightweightTheme = new LightweightTheme(this);
}
+ public static void createShortcut() {
+ final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+ if (selectedTab != null) {
+ createShortcut(selectedTab.getTitle(), selectedTab.getURL());
+ }
+ }
+
// Creates a homescreen shortcut for a web page.
// This is the entry point from nsIShellService.
@WrapForJNI(calledFrom = "gecko")
public static void createShortcut(final String title, final String url) {
final Tab selectedTab = Tabs.getInstance().getSelectedTab();
final String manifestUrl = selectedTab.getManifestUrl();
if (manifestUrl != null) {
--- a/mobile/android/base/java/org/mozilla/gecko/Tab.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tab.java
@@ -18,16 +18,17 @@ import org.mozilla.gecko.gfx.BitmapUtils
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconDescriptor;
import org.mozilla.gecko.icons.IconRequestBuilder;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.reader.ReaderModeUtils;
import org.mozilla.gecko.reader.ReadingListHelper;
import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
+import org.mozilla.gecko.toolbar.PageActionLayout;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.SiteLogins;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -458,16 +459,26 @@ public class Tab {
}
public void setHasFeeds(boolean hasFeeds) {
mHasFeeds = hasFeeds;
}
public void setManifestUrl(String manifestUrl) {
mManifestUrl = manifestUrl;
+ updatePageAction();
+ }
+
+ public void updatePageAction() {
+ if (mManifestUrl != null) {
+ PageActionLayout.PageAction.showPwaPageAction();
+
+ } else {
+ PageActionLayout.PageAction.clearPwaPageAction();
+ }
}
public void setHasOpenSearch(boolean hasOpenSearch) {
mHasOpenSearch = hasOpenSearch;
}
public void setLoadedFromCache(boolean loadedFromCache) {
mLoadedFromCache = loadedFromCache;
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -305,16 +305,18 @@ public class Tabs implements BundleEvent
// This avoids a NPE below, but callers need to be careful to
// handle this case.
if (tab == null || oldTab == tab) {
return tab;
}
mSelectedTab = tab;
+ mSelectedTab.updatePageAction();
+
notifyListeners(tab, TabEvents.SELECTED);
if (mLayerView != null) {
mLayerView.setClearColor(getTabColor(tab));
}
if (oldTab != null) {
notifyListeners(oldTab, TabEvents.UNSELECTED);
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
@@ -1,16 +1,17 @@
/* -*- 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.toolbar;
import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu;
@@ -103,24 +104,33 @@ public class PageActionLayout extends Th
@Override // BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
ThreadUtils.assertOnUiThread();
if ("PageActions:Add".equals(event)) {
final String id = message.getString("id");
+
+ boolean alreadyAdded = isPwaAdded(id);
+ if (alreadyAdded) {
+ return;
+ }
final String title = message.getString("title");
final String imageURL = message.getString("icon");
final boolean important = message.getBoolean("important");
final boolean useTint = message.getBoolean("useTint");
addPageAction(id, title, imageURL, useTint, new OnPageActionClickListeners() {
@Override
public void onClick(final String id) {
+ if (id != null && id.equals(PageAction.UUID_PAGE_ACTION_PWA)) {
+ GeckoApplication.createShortcut();
+ return;
+ }
final GeckoBundle data = new GeckoBundle(1);
data.putString("id", id);
EventDispatcher.getInstance().dispatch("PageActions:Clicked", data);
}
@Override
public boolean onLongClick(String id) {
final GeckoBundle data = new GeckoBundle(1);
@@ -130,16 +140,25 @@ public class PageActionLayout extends Th
}
}, important);
} else if ("PageActions:Remove".equals(event)) {
removePageAction(message.getString("id"));
}
}
+ private boolean isPwaAdded(String id) {
+ for (PageAction pageAction : mPageActionList) {
+ if (pageAction.getID() != null && pageAction.getID().equals(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void addPageAction(final String id, final String title, final String imageData, final boolean useTint,
final OnPageActionClickListeners onPageActionClickListeners, boolean important) {
ThreadUtils.assertOnUiThread();
final PageAction pageAction = new PageAction(id, title, null, onPageActionClickListeners, important);
int insertAt = mPageActionList.size();
while (insertAt > 0 && mPageActionList.get(insertAt - 1).isImportant()) {
@@ -323,17 +342,19 @@ public class PageActionLayout extends Th
mPageActionsMenu.show();
}
private static interface OnPageActionClickListeners {
public void onClick(String id);
public boolean onLongClick(String id);
}
- private static class PageAction {
+ public static class PageAction {
+ public static final String UUID_PAGE_ACTION_PWA = "279c269d-6397-4f86-a6d2-452e26456d4a";
+
private final OnPageActionClickListeners mOnPageActionClickListeners;
private Drawable mDrawable;
private final String mTitle;
private final String mId;
private final int key;
private final boolean mImportant;
public PageAction(String id,
@@ -345,16 +366,25 @@ public class PageActionLayout extends Th
mTitle = title;
mDrawable = image;
mOnPageActionClickListeners = onPageActionClickListeners;
mImportant = important;
key = UUID.fromString(mId.subSequence(1, mId.length() - 2).toString()).hashCode();
}
+ public static void showPwaPageAction() {
+ GeckoBundle bundle = new GeckoBundle();
+ bundle.putString("id", UUID_PAGE_ACTION_PWA);
+ bundle.putString("title", "Add PWA Shortcut");
+ bundle.putString("icon", "drawable://icon_openinapp");
+ bundle.putBoolean("important", true);
+ EventDispatcher.getInstance().dispatch("PageActions:Add", bundle);
+ }
+
public Drawable getDrawable() {
return mDrawable;
}
public void setDrawable(Drawable d) {
mDrawable = d;
}
@@ -381,10 +411,16 @@ public class PageActionLayout extends Th
}
public boolean onLongClick() {
if (mOnPageActionClickListeners != null) {
return mOnPageActionClickListeners.onLongClick(mId);
}
return false;
}
+
+ public static void clearPwaPageAction() {
+ GeckoBundle bundle = new GeckoBundle();
+ bundle.putString("id", UUID_PAGE_ACTION_PWA);
+ EventDispatcher.getInstance().dispatch("PageActions:Remove", bundle);
+ }
}
}