Bug 1220928 - Add empty state. r=sebastian
MozReview-Commit-ID: Ger04bA0aaC
--- 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
@@ -8,48 +8,64 @@ package org.mozilla.gecko.home;
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.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.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);
}
@@ -67,19 +83,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();
}
@@ -148,35 +162,47 @@ 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(OnPanelLevelChangeListener.PanelLevel.PARENT);
+ updateEmptyView(mAdapter.getItemCount() == 0);
}
}
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) {
+ 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:
@@ -221,9 +247,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"/>