Bug 1246239 - Show "saved offline" snackbar when readermode page is bookmarked r=sebastian
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 12 Apr 2016 16:18:50 -0700
changeset 331292 55f774304456fba311e048824adf6df207c5d760
parent 331291 90a025addc58d0bbbbcde962f209dfea1dde81da
child 331293 159d0cb3c2c360ec38ff965a50a67fc2b5ab736b
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1246239
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1246239 - Show "saved offline" snackbar when readermode page is bookmarked r=sebastian MozReview-Commit-ID: IC33HwSClcI
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/SnackbarHelper.java
mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -2,38 +2,36 @@
  * 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;
 
 import android.Manifest;
 import android.app.DownloadManager;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.os.Environment;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 import org.json.JSONArray;
 import org.mozilla.gecko.adjust.AdjustHelperInterface;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
-import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.cleanup.FileCleanupController;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.SuggestedSites;
 import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.distribution.Distribution.DistributionDescriptor;
 import org.mozilla.gecko.distribution.DistributionStoreCallback;
 import org.mozilla.gecko.dlc.DownloadContentService;
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.feeds.FeedService;
 import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
 import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
@@ -81,16 +79,17 @@ import org.mozilla.gecko.telemetry.Telem
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.toolbar.ToolbarProgressView;
 import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.Clipboard;
+import org.mozilla.gecko.util.DrawableUtil;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.Experiments;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.NativeEventListener;
@@ -125,16 +124,17 @@ import android.nfc.NfcAdapter;
 import android.nfc.NfcEvent;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.StrictMode;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.MenuItemCompat;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Base64;
 import android.util.Base64OutputStream;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.KeyEvent;
@@ -358,17 +358,25 @@ public class BrowserApp extends GeckoApp
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     invalidateOptionsMenu();
                 }
                 break;
             case PAGE_SHOW:
                 tab.loadFavicon();
                 break;
             case BOOKMARK_ADDED:
-                showBookmarkAddedSnackbar();
+                // We always show the special offline snackbar whenever we bookmark a reader page.
+                // It's possible that the page is already stored offline, however this is highly
+                // unlikely, and even so it is probably nicer to show the same offline notification
+                // every time we bookmark an about:reader page.
+                if (!AboutPages.isAboutReader(tab.getURL())) {
+                    showBookmarkAddedSnackbar();
+                } else {
+                    showReaderModeBookmarkAddedSnackbar();
+                }
                 break;
             case BOOKMARK_REMOVED:
                 showBookmarkRemovedSnackbar();
                 break;
 
             case UNSELECTED:
                 // We receive UNSELECTED immediately after the SELECTED listeners run
                 // so we are ensured that the unselectedTabEditingText has not changed.
@@ -437,16 +445,36 @@ public class BrowserApp extends GeckoApp
 
         SnackbarHelper.showSnackbarWithAction(this,
                 getResources().getString(R.string.bookmark_added),
                 Snackbar.LENGTH_LONG,
                 getResources().getString(R.string.bookmark_options),
                 callback);
     }
 
+    private void showReaderModeBookmarkAddedSnackbar() {
+        final Drawable iconDownloaded = DrawableUtil.tintDrawable(getContext(), R.drawable.status_icon_readercache, Color.WHITE);
+
+        final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
+            @Override
+            public void onClick(View v) {
+                openUrlAndStopEditing("about:home?panel=" + HomeConfig.getIdForBuiltinPanelType(PanelType.BOOKMARKS));
+            }
+        };
+
+        SnackbarHelper.showSnackbarWithActionAndColors(this,
+                getResources().getString(R.string.reader_saved_offline),
+                Snackbar.LENGTH_LONG,
+                getResources().getString(R.string.reader_switch_to_bookmarks),
+                callback,
+                iconDownloaded,
+                ContextCompat.getColor(this, R.color.link_blue),
+                Color.WHITE);
+    }
+
     private void showBookmarkRemovedSnackbar() {
         SnackbarHelper.showSnackbar(this, getResources().getString(R.string.bookmark_removed), Snackbar.LENGTH_LONG);
     }
 
     @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         if (AndroidGamepadManager.handleKeyEvent(event)) {
             return true;
--- a/mobile/android/base/java/org/mozilla/gecko/SnackbarHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SnackbarHelper.java
@@ -4,20 +4,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeJSObject;
 
 import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
 import android.support.design.widget.Snackbar;
 import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
+import android.util.TypedValue;
 import android.view.View;
+import android.widget.TextView;
 
 import java.lang.ref.WeakReference;
 
 /**
  * Helper class for creating and dismissing snackbars. Use this class to guarantee a consistent style and behavior
  * across the app.
  */
 public class SnackbarHelper {
@@ -93,25 +97,56 @@ public class SnackbarHelper {
      *
      * @param activity Activity to show the snackbar in.
      * @param message The text to show. Can be formatted text.
      * @param duration How long to display the message.
      * @param action Action text to display.
      * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
      */
     public static void showSnackbarWithAction(Activity activity, String message, int duration, String action, SnackbarCallback callback) {
+        showSnackbarWithActionAndColors(activity, message, duration, action, callback, null, null, null);
+    }
+
+
+    public static void showSnackbarWithActionAndColors(Activity activity,
+                                                       String message,
+                                                       int duration,
+                                                       String action,
+                                                       SnackbarCallback callback,
+                                                       Drawable icon,
+                                                       Integer backgroundColor,
+                                                       Integer actionColor) {
         final View parentView = findBestParentView(activity);
         final Snackbar snackbar = Snackbar.make(parentView, message, duration);
 
         if (callback != null && !TextUtils.isEmpty(action)) {
             snackbar.setAction(action, callback);
-            snackbar.setActionTextColor(ContextCompat.getColor(activity, R.color.fennec_ui_orange));
+            if (actionColor == null) {
+                ContextCompat.getColor(activity, R.color.fennec_ui_orange);
+            } else {
+                snackbar.setActionTextColor(actionColor);
+            }
             snackbar.setCallback(callback);
         }
 
+        if (icon != null) {
+            int leftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, activity.getResources().getDisplayMetrics());
+
+            final InsetDrawable paddedIcon = new InsetDrawable(icon, 0, 0, leftPadding, 0);
+
+            paddedIcon.setBounds(0, 0, leftPadding + icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+
+            TextView textView = (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text);
+            textView.setCompoundDrawables(paddedIcon, null, null, null);
+        }
+
+        if (backgroundColor != null) {
+            snackbar.getView().setBackgroundColor(backgroundColor);
+        }
+
         snackbar.show();
 
         synchronized (currentSnackbarLock) {
             currentSnackbar = new WeakReference<>(snackbar);
         }
     }
 
     /**
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
@@ -140,17 +140,17 @@ class SearchEngineRow extends AnimatedHe
         mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout);
         mIconView = (FaviconView) findViewById(R.id.suggestion_icon);
 
         // User-entered search term is first suggestion
         mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered);
         mUserEnteredView.setOnClickListener(mClickListener);
 
         mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
-        mSearchHistorySuggestionIcon = DrawableUtil.tintDrawable(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
+        mSearchHistorySuggestionIcon = DrawableUtil.tintDrawableWithColorRes(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
 
         // Suggestion limits
         mMaxSavedSuggestions = getResources().getInteger(R.integer.max_saved_suggestions);
         mMaxSearchSuggestions = getResources().getInteger(R.integer.max_search_suggestions);
     }
 
     private void setDescriptionOnSuggestion(View v, String suggestion) {
         v.setContentDescription(getResources().getString(R.string.suggestion_for_engine,
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
@@ -145,20 +145,20 @@ public class ToolbarEditLayout extends T
         if (!HardwareUtils.isTablet()) {
             return;
         }
 
         // When on tablet show a magnifying glass in editing mode
         final int searchDrawableId = R.drawable.search_icon_active;
         final Drawable searchDrawable;
         if (!isActive) {
-            searchDrawable = DrawableUtil.tintDrawable(getContext(), searchDrawableId, R.color.placeholder_grey);
+            searchDrawable = DrawableUtil.tintDrawableWithColorRes(getContext(), searchDrawableId, R.color.placeholder_grey);
         } else {
             if (isPrivateMode()) {
-                searchDrawable = DrawableUtil.tintDrawable(getContext(), searchDrawableId, R.color.tabs_tray_icon_grey);
+                searchDrawable = DrawableUtil.tintDrawableWithColorRes(getContext(), searchDrawableId, R.color.tabs_tray_icon_grey);
             } else {
                 searchDrawable = getResources().getDrawable(searchDrawableId);
             }
         }
 
         mSearchIcon.setImageDrawable(searchDrawable);
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java
@@ -2,39 +2,52 @@
 /* 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.util;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.CheckResult;
+import android.support.annotation.ColorInt;
 import android.support.annotation.ColorRes;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
 
 public class DrawableUtil {
 
     /**
      * Tints the given drawable with the given color and returns it.
      */
     @CheckResult
-    public static Drawable tintDrawable(@NonNull final Context context, @DrawableRes final int drawableID,
-                @ColorRes final int colorID) {
+    public static Drawable tintDrawable(@NonNull final Context context,
+                                        @DrawableRes final int drawableID,
+                                        @ColorInt final int color) {
         final Drawable icon = DrawableCompat.wrap(
                 ContextCompat.getDrawable(context, drawableID).mutate());
-        DrawableCompat.setTint(icon, ContextCompat.getColor(context, colorID));
+        DrawableCompat.setTint(icon, color);
         return icon;
     }
 
     /**
+     * Tints the given drawable with the given color and returns it.
+     */
+    @CheckResult
+    public static Drawable tintDrawableWithColorRes(@NonNull final Context context,
+                                                    @DrawableRes final int drawableID,
+                                                    @ColorRes final int colorID) {
+        return tintDrawable(context, drawableID, ContextCompat.getColor(context, colorID));
+    }
+
+    /**
      * Tints the given drawable with the given tint list and returns it. Note that you
      * should no longer use the argument Drawable because the argument is not mutated
      * on pre-Lollipop devices but is mutated on L+ due to differences in the Support
      * Library implementation (bug 1193950).
      */
     @CheckResult
     public static Drawable tintDrawableWithStateList(@NonNull final Drawable drawable,
             @NonNull final ColorStateList colorList) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -65,16 +65,22 @@
 <!ENTITY bookmark_options "Options">
 <!ENTITY screenshot_added_to_bookmarks "Screenshot added to bookmarks">
 <!-- Localization note (screenshot_folder_label_in_bookmarks): We save links to screenshots
      the user takes. The folder we store these links in is located in the bookmarks list
      and is labeled by this String. -->
 <!ENTITY screenshot_folder_label_in_bookmarks "Screenshots">
 <!ENTITY readinglist_smartfolder_label_in_bookmarks "Reading List">
 
+<!ENTITY reader_saved_offline "Saved offline">
+<!-- Localization note (reader_switch_to_bookmarks) : This
+     string is used as an action in a snackbar - it lets you
+     "switch" to the bookmarks (saved items) panel. -->
+<!ENTITY reader_switch_to_bookmarks "Switch">
+
 <!ENTITY history_today_section "Today">
 <!ENTITY history_yesterday_section "Yesterday">
 <!ENTITY history_week_section3 "Last 7 days">
 <!ENTITY history_older_section3 "Older than 6 months">
 
 <!ENTITY search "Search">
 <!ENTITY reload "Reload">
 <!ENTITY forward "Forward">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -87,16 +87,19 @@
   <string name="bookmark_already_added">&bookmark_already_added;</string>
   <string name="bookmark_removed">&bookmark_removed;</string>
   <string name="bookmark_updated">&bookmark_updated;</string>
   <string name="bookmark_options">&bookmark_options;</string>
   <string name="screenshot_added_to_bookmarks">&screenshot_added_to_bookmarks;</string>
   <string name="screenshot_folder_label_in_bookmarks">&screenshot_folder_label_in_bookmarks;</string>
   <string name="readinglist_smartfolder_label_in_bookmarks">&readinglist_smartfolder_label_in_bookmarks;</string>
 
+  <string name="reader_saved_offline">&reader_saved_offline;</string>
+  <string name="reader_switch_to_bookmarks">&reader_switch_to_bookmarks;</string>
+
   <string name="history_today_section">&history_today_section;</string>
   <string name="history_yesterday_section">&history_yesterday_section;</string>
   <string name="history_week_section">&history_week_section3;</string>
   <string name="history_older_section">&history_older_section3;</string>
 
   <string name="share">&share;</string>
   <string name="share_title">&share_title;</string>
   <string name="share_image_failed">&share_image_failed;</string>