Bug 1251362 - Part 13 - Hide Recent Tabs smart folder if there aren't any closed tabs to be shown. r=liuche
This involves making the number of visible smart folders dynamic, so the history adapter can properly display its contents.
MozReview-Commit-ID: 6b4V6IHB7BE
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
@@ -3,31 +3,32 @@
* 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;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.database.Cursor;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.util.DrawableUtil;
+import org.mozilla.gecko.util.ThreadUtils;
public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistoryItem> implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder {
private static final int RECENT_TABS_SMARTFOLDER_INDEX = 0;
- private static final int SYNCED_DEVICES_SMARTFOLDER_INDEX = 1;
// Array for the time ranges in milliseconds covered by each section.
static final HistorySectionsHelper.SectionDateRange[] sectionDateRangeArray = new HistorySectionsHelper.SectionDateRange[SectionHeader.values().length];
// Semantic names for the time covered by each section
public enum SectionHeader {
TODAY,
YESTERDAY,
@@ -42,70 +43,125 @@ public class CombinedHistoryAdapter exte
}
private Cursor historyCursor;
private DevicesUpdateHandler devicesUpdateHandler;
private int deviceCount = 0;
private RecentTabsUpdateHandler recentTabsUpdateHandler;
private int recentTabsCount = 0;
+ private LinearLayoutManager linearLayoutManager;
+
// android:backgroundTint only works in Android 21 and higher, so we can't do this statically in the xml.
private Drawable recentTabsIcon;
// We use a sparse array to store each section header's position in the panel [more cheaply than a HashMap].
private final SparseArray<SectionHeader> sectionHeaders;
public CombinedHistoryAdapter(Context context, Resources resources) {
super();
sectionHeaders = new SparseArray<>();
HistorySectionsHelper.updateRecentSectionOffset(resources, sectionDateRangeArray);
// Colors chosen so that the end result after tinting is disabled_grey (#BFBFBF).
recentTabsIcon = DrawableUtil.tintDrawable(context, R.drawable.icon_most_recent_empty, Color.rgb(168, 168, 168));
}
+ public void setLinearLayoutManager(LinearLayoutManager linearLayoutManager) {
+ this.linearLayoutManager = linearLayoutManager;
+ }
+
public void setHistory(Cursor history) {
historyCursor = history;
populateSectionHeaders(historyCursor, sectionHeaders);
notifyDataSetChanged();
}
public interface DevicesUpdateHandler {
void onDeviceCountUpdated(int count);
}
public DevicesUpdateHandler getDeviceUpdateHandler() {
if (devicesUpdateHandler == null) {
devicesUpdateHandler = new DevicesUpdateHandler() {
@Override
public void onDeviceCountUpdated(int count) {
deviceCount = count;
- notifyItemChanged(SYNCED_DEVICES_SMARTFOLDER_INDEX);
+ notifyItemChanged(getSyncedDevicesSmartfolderIndex());
}
};
}
return devicesUpdateHandler;
}
public interface RecentTabsUpdateHandler {
void onRecentTabsCountUpdated(int count);
}
public RecentTabsUpdateHandler getRecentTabsUpdateHandler() {
if (recentTabsUpdateHandler == null) {
recentTabsUpdateHandler = new RecentTabsUpdateHandler() {
@Override
- public void onRecentTabsCountUpdated(int count) {
- recentTabsCount = count;
- notifyItemChanged(RECENT_TABS_SMARTFOLDER_INDEX);
+ public void onRecentTabsCountUpdated(final int count) {
+ // Now that other items can move around depending on the visibility of the
+ // Recent Tabs folder, only update the recentTabsCount on the UI thread.
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final boolean prevFolderVisibility = isRecentTabsFolderVisible();
+ recentTabsCount = count;
+
+ if (prevFolderVisibility == isRecentTabsFolderVisible()) {
+ notifyItemChanged(RECENT_TABS_SMARTFOLDER_INDEX);
+ } else { // Visibility changed.
+ // If the Recent Tabs smart folder has become hidden/unhidden,
+ // we need to recalculate the history section header positions.
+ populateSectionHeaders(historyCursor, sectionHeaders);
+
+ if (isRecentTabsFolderVisible()) {
+ notifyItemInserted(RECENT_TABS_SMARTFOLDER_INDEX);
+ // If the list exceeds the display height and we're showing the first item,
+ // we need to manually scroll up after inserting a new item at position 0
+ // (see https://code.google.com/p/android/issues/detail?id=174227#c2).
+ if (linearLayoutManager != null &&
+ linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
+ linearLayoutManager.scrollToPosition(0);
+ }
+ } else {
+ notifyItemRemoved(RECENT_TABS_SMARTFOLDER_INDEX);
+ }
+ }
+ }
+ });
}
};
}
return recentTabsUpdateHandler;
}
+ private boolean isRecentTabsFolderVisible() {
+ return recentTabsCount > 0;
+ }
+
+ // Number of smart folders for determining practical empty state.
+ public int getNumVisibleSmartFolders() {
+ int visibleFolders = 1; // Synced devices folder is always visible.
+
+ if (isRecentTabsFolderVisible()) {
+ visibleFolders += 1;
+ }
+
+ return visibleFolders;
+ }
+
+ private int getSyncedDevicesSmartfolderIndex() {
+ return isRecentTabsFolderVisible() ?
+ RECENT_TABS_SMARTFOLDER_INDEX + 1 :
+ RECENT_TABS_SMARTFOLDER_INDEX;
+ }
+
@Override
public CombinedHistoryItem onCreateViewHolder(ViewGroup viewGroup, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
final View view;
final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
switch (itemType) {
@@ -162,27 +218,27 @@ public class CombinedHistoryAdapter exte
* @param type ItemType of the item
* @param position position in the adapter
* @return position of the item in the data structure
*/
private int transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType type, int position) {
if (type == CombinedHistoryItem.ItemType.SECTION_HEADER) {
return position;
} else if (type == CombinedHistoryItem.ItemType.HISTORY) {
- return position - getHeadersBefore(position) - CombinedHistoryPanel.NUM_SMART_FOLDERS;
+ return position - getHeadersBefore(position) - getNumVisibleSmartFolders();
} else {
return position;
}
}
private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == RECENT_TABS_SMARTFOLDER_INDEX) {
+ if (position == RECENT_TABS_SMARTFOLDER_INDEX && isRecentTabsFolderVisible()) {
return CombinedHistoryItem.ItemType.RECENT_TABS;
}
- if (position == SYNCED_DEVICES_SMARTFOLDER_INDEX) {
+ if (position == getSyncedDevicesSmartfolderIndex()) {
return CombinedHistoryItem.ItemType.SYNCED_DEVICES;
}
final int sectionPosition = transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType.SECTION_HEADER, position);
if (sectionHeaders.get(sectionPosition) != null) {
return CombinedHistoryItem.ItemType.SECTION_HEADER;
}
return CombinedHistoryItem.ItemType.HISTORY;
}
@@ -190,42 +246,42 @@ public class CombinedHistoryAdapter exte
@Override
public int getItemViewType(int position) {
return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
}
@Override
public int getItemCount() {
final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
- return historySize + sectionHeaders.size() + CombinedHistoryPanel.NUM_SMART_FOLDERS;
+ return historySize + sectionHeaders.size() + getNumVisibleSmartFolders();
}
/**
* Add only the SectionHeaders that have history items within their range to a SparseArray, where the
* array index is the position of the header in the history-only (no clients) ordering.
* @param c data Cursor
* @param sparseArray SparseArray to populate
*/
- private static void populateSectionHeaders(Cursor c, SparseArray<SectionHeader> sparseArray) {
+ private void populateSectionHeaders(Cursor c, SparseArray<SectionHeader> sparseArray) {
sparseArray.clear();
if (c == null || !c.moveToFirst()) {
return;
}
SectionHeader section = null;
do {
final int historyPosition = c.getPosition();
final long visitTime = c.getLong(c.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
final SectionHeader itemSection = getSectionFromTime(visitTime);
if (section != itemSection) {
section = itemSection;
- sparseArray.append(historyPosition + sparseArray.size() + CombinedHistoryPanel.NUM_SMART_FOLDERS, section);
+ sparseArray.append(historyPosition + sparseArray.size() + getNumVisibleSmartFolders(), section);
}
if (section == SectionHeader.OLDER_THAN_SIX_MONTHS) {
break;
}
} while (c.moveToNext());
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
@@ -63,19 +63,16 @@ public class CombinedHistoryPanel extend
private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "clients", "tabs" };
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";
- // Number of smart folders for determining practical empty state.
- public static final int NUM_SMART_FOLDERS = 2;
-
private CombinedHistoryRecyclerView mRecyclerView;
private CombinedHistoryAdapter mHistoryAdapter;
private ClientsAdapter mClientsAdapter;
private RecentTabsAdapter mRecentTabsAdapter;
private CursorLoaderCallbacks mCursorLoaderCallbacks;
private PanelLevel mPanelLevel;
private Button mPanelFooterButton;
@@ -158,16 +155,17 @@ public class CombinedHistoryPanel extend
mPanelLevel == PanelLevel.CHILD_SYNC ? mClientsAdapter : mRecentTabsAdapter);
final RecyclerView.ItemAnimator animator = new DefaultItemAnimator();
animator.setAddDuration(100);
animator.setChangeDuration(100);
animator.setMoveDuration(100);
animator.setRemoveDuration(100);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ mHistoryAdapter.setLinearLayoutManager((LinearLayoutManager) mRecyclerView.getLayoutManager());
mRecyclerView.setItemAnimator(animator);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getContext()));
mRecyclerView.setOnHistoryClickedListener(mUrlOpenListener);
mRecyclerView.setOnPanelLevelChangeListener(new OnLevelChangeListener());
mRecyclerView.setHiddenClientsDialogBuilder(new HiddenClientsHelper());
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
@@ -361,17 +359,17 @@ public class CombinedHistoryPanel extend
return true;
}
}
private void updateButtonFromLevel() {
switch (mPanelLevel) {
case PARENT:
final boolean historyRestricted = !Restrictions.isAllowed(getActivity(), Restrictable.CLEAR_HISTORY);
- if (historyRestricted || mHistoryAdapter.getItemCount() == NUM_SMART_FOLDERS) {
+ if (historyRestricted || mHistoryAdapter.getItemCount() == mHistoryAdapter.getNumVisibleSmartFolders()) {
mPanelFooterButton.setVisibility(View.GONE);
} else {
mPanelFooterButton.setText(R.string.home_clear_history_button);
mPanelFooterButton.setVisibility(View.VISIBLE);
}
break;
case CHILD_RECENT_TABS:
if (mRecentTabsAdapter.getClosedTabsCount() > 1) {
@@ -430,17 +428,17 @@ public class CombinedHistoryPanel extend
}
private void updateEmptyView() {
boolean showEmptyHistoryView = false;
boolean showEmptyClientsView = false;
boolean showEmptyRecentTabsView = false;
switch (mPanelLevel) {
case PARENT:
- showEmptyHistoryView = mHistoryAdapter.getItemCount() == NUM_SMART_FOLDERS;
+ showEmptyHistoryView = mHistoryAdapter.getItemCount() == mHistoryAdapter.getNumVisibleSmartFolders();
break;
case CHILD_SYNC:
showEmptyClientsView = mClientsAdapter.getItemCount() == 1;
break;
case CHILD_RECENT_TABS:
showEmptyRecentTabsView = mRecentTabsAdapter.getClosedTabsCount() == 0;