Bug 1220928 - Add empty state. r=sebastian
authorChenxia Liu <liuche@mozilla.com>
Tue, 15 Mar 2016 11:45:21 -0700
changeset 291325 5539cc5e598073da1b0d53a35596f00bca92cff5
parent 291324 1ee51352897e5ed2e1d0f1277c35937758c5d42e
child 291326 0242801375876b7f7a711546f543f361282c4ece
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1220928
milestone48.0a1
Bug 1220928 - Add empty state. r=sebastian MozReview-Commit-ID: Ger04bA0aaC
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
mobile/android/base/resources/layout/home_combined_history_panel.xml
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
@@ -145,16 +145,23 @@ public class CombinedHistoryAdapter exte
             return (clientChildren == null) ? 0 : clientChildren.size();
         } else {
             final int remoteSize = remoteClients.size();
             final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
             return remoteSize + historySize;
         }
     }
 
+    public boolean containsHistory() {
+        if (historyCursor == null) {
+            return false;
+        }
+        return (historyCursor.getCount() > 0);
+    }
+
     @Override
     public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {
         final ItemType itemType = ItemType.viewTypeToItemType(getItemViewType(position));
         final int localPosition = transformPosition(itemType, position);
 
         switch (itemType) {
             case CLIENT:
                 final CombinedHistoryItem.ClientItem clientItem = (CombinedHistoryItem.ClientItem) viewHolder;
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
@@ -9,49 +9,65 @@ import android.app.AlertDialog;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.Loader;
 import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.RecyclerView;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.UnderlineSpan;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONArray;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.Restrictions;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.RemoteClient;
+import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.widget.DividerItemDecoration;
 
 import java.util.Collections;
 import java.util.List;
 
 public class CombinedHistoryPanel extends HomeFragment {
     private static final String LOGTAG = "GeckoCombinedHistoryPnl";
     private final int LOADER_ID_HISTORY = 0;
     private final int LOADER_ID_REMOTE = 1;
 
+    // String placeholders to mark formatting.
+    private final static String FORMAT_S1 = "%1$s";
+    private final static String FORMAT_S2 = "%2$s";
+
     private CombinedHistoryRecyclerView mRecyclerView;
     private CombinedHistoryAdapter mAdapter;
     private CursorLoaderCallbacks mCursorLoaderCallbacks;
 
     private OnPanelLevelChangeListener.PanelLevel mPanelLevel = OnPanelLevelChangeListener.PanelLevel.PARENT;
     private Button mPanelFooterButton;
+    // Reference to the View to display when there are no results.
+    private View mEmptyView;
 
     public interface OnPanelLevelChangeListener {
         enum PanelLevel {
         PARENT, CHILD
         }
 
         void onPanelLevelChange(PanelLevel level);
     }
@@ -69,19 +85,17 @@ public class CombinedHistoryPanel extend
         mAdapter = new CombinedHistoryAdapter(getContext());
         mRecyclerView.setAdapter(mAdapter);
         mRecyclerView.setItemAnimator(new DefaultItemAnimator());
         mRecyclerView.addItemDecoration(new DividerItemDecoration(getContext()));
         mRecyclerView.setOnHistoryClickedListener(mUrlOpenListener);
         mRecyclerView.setOnPanelLevelChangeListener(new OnLevelChangeListener());
         mPanelFooterButton = (Button) view.findViewById(R.id.clear_history_button);
         mPanelFooterButton.setOnClickListener(new OnFooterButtonClickListener());
-        mPanelFooterButton.setVisibility(View.VISIBLE);
 
-        // TODO: Check if empty state
         // TODO: Handle date headers.
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         mCursorLoaderCallbacks = new CursorLoaderCallbacks();
     }
@@ -150,40 +164,53 @@ public class CombinedHistoryPanel extend
                 case LOADER_ID_HISTORY:
                     mAdapter.setHistory(c);
                     break;
 
                 case LOADER_ID_REMOTE:
                     final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
                     // TODO: Handle hidden clients
                     mAdapter.setClients(clients);
-
                     break;
             }
 
+            // Check and set empty state.
+            updateButtonFromLevel(mPanelLevel);
+            updateEmptyView(mAdapter.getItemCount() == 0);
         }
 
         public void onLoaderReset(Loader<Cursor> c) {
             mAdapter.setClients(Collections.<RemoteClient>emptyList());
             mAdapter.setHistory(null);
         }
     }
 
     protected class OnLevelChangeListener implements OnPanelLevelChangeListener {
         @Override
         public void onPanelLevelChange(PanelLevel level) {
-            mPanelLevel = level;
-            switch (mPanelLevel) {
-                case PARENT:
+            updateButtonFromLevel(level);
+        }
+    }
+
+    private void updateButtonFromLevel(OnPanelLevelChangeListener.PanelLevel level) {
+        mPanelLevel = level;
+        switch (level) {
+            case CHILD:
+                mPanelFooterButton.setVisibility(View.VISIBLE);
+                mPanelFooterButton.setText(R.string.home_open_all);
+                break;
+            case PARENT:
+                final boolean historyRestricted = !Restrictions.isAllowed(getActivity(), Restrictable.CLEAR_HISTORY);
+                if (historyRestricted || !mAdapter.containsHistory()) {
+                    mPanelFooterButton.setVisibility(View.GONE);
+                } else {
+                    mPanelFooterButton.setVisibility(View.VISIBLE);
                     mPanelFooterButton.setText(R.string.home_clear_history_button);
-                    break;
-                case CHILD:
-                    mPanelFooterButton.setText(R.string.home_open_all);
-                    break;
-            }
+                }
+                break;
         }
     }
 
     private class OnFooterButtonClickListener implements View.OnClickListener {
         @Override
         public void onClick(View view) {
             switch (mPanelLevel) {
                 case PARENT:
@@ -228,9 +255,100 @@ public class CombinedHistoryPanel extend
                         } catch (JSONException e) {
                             Log.e(LOGTAG, "Error making JSON message to open tabs");
                         }
                     }
                     break;
             }
         }
     }
+
+    private void updateEmptyView(boolean isEmpty) {
+        if (isEmpty) {
+            if (mEmptyView == null) {
+                // Set empty panel view if it needs to be shown and hasn't been inflated.
+                final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
+                mEmptyView = emptyViewStub.inflate();
+
+                final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
+                emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
+
+                final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
+                emptyText.setText(R.string.home_most_recent_empty);
+
+                final TextView emptyHint = (TextView) mEmptyView.findViewById(R.id.home_empty_hint);
+                final String hintText = getResources().getString(R.string.home_most_recent_emptyhint);
+
+                final SpannableStringBuilder hintBuilder = formatHintText(hintText);
+                if (hintBuilder != null) {
+                    emptyHint.setText(hintBuilder);
+                    emptyHint.setMovementMethod(LinkMovementMethod.getInstance());
+                    emptyHint.setVisibility(View.VISIBLE);
+                }
+
+                if (!Restrictions.isAllowed(getActivity(), Restrictable.PRIVATE_BROWSING)) {
+                    emptyHint.setVisibility(View.GONE);
+                }
+                mEmptyView.setVisibility(View.VISIBLE);
+            } else {
+                if (mEmptyView != null) {
+                    mEmptyView.setVisibility(View.GONE);
+                }
+            }
+        }
+    }
+    /**
+     * Make Span that is clickable, and underlined
+     * between the string markers <code>FORMAT_S1</code> and
+     * <code>FORMAT_S2</code>.
+     *
+     * @param text String to format
+     * @return formatted SpannableStringBuilder, or null if there
+     * is not any text to format.
+     */
+    private SpannableStringBuilder formatHintText(String text) {
+        // Set formatting as marked by string placeholders.
+        final int underlineStart = text.indexOf(FORMAT_S1);
+        final int underlineEnd = text.indexOf(FORMAT_S2);
+
+        // Check that there is text to be formatted.
+        if (underlineStart >= underlineEnd) {
+            return null;
+        }
+
+        final SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+
+        // Set clickable text.
+        final ClickableSpan clickableSpan = new ClickableSpan() {
+            @Override
+            public void onClick(View widget) {
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.PANEL, "hint-private-browsing");
+                try {
+                    final JSONObject json = new JSONObject();
+                    json.put("type", "Menu:Open");
+                    EventDispatcher.getInstance().dispatchEvent(json, null);
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "Error forming JSON for Private Browsing contextual hint", e);
+                }
+            }
+        };
+
+        ssb.setSpan(clickableSpan, 0, text.length(), 0);
+
+        // Remove underlining set by ClickableSpan.
+        final UnderlineSpan noUnderlineSpan = new UnderlineSpan() {
+            @Override
+            public void updateDrawState(TextPaint textPaint) {
+                textPaint.setUnderlineText(false);
+            }
+        };
+
+        ssb.setSpan(noUnderlineSpan, 0, text.length(), 0);
+
+        // Add underlining for "Private Browsing".
+        ssb.setSpan(new UnderlineSpan(), underlineStart, underlineEnd, 0);
+
+        ssb.delete(underlineEnd, underlineEnd + FORMAT_S2.length());
+        ssb.delete(underlineStart, underlineStart + FORMAT_S1.length());
+
+        return ssb;
+    }
 }
--- a/mobile/android/base/resources/layout/home_combined_history_panel.xml
+++ b/mobile/android/base/resources/layout/home_combined_history_panel.xml
@@ -3,16 +3,21 @@
    - 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/. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:orientation="vertical">
 
+    <ViewStub android:id="@+id/home_empty_view_stub"
+              android:layout="@layout/home_empty_panel"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"/>
+
     <org.mozilla.gecko.home.CombinedHistoryRecyclerView
             android:id="@+id/combined_recycler_view"
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"/>
 
     <include layout="@layout/home_history_clear_button"/>