Bug 1251362 - Part 13 - Hide Recent Tabs smart folder if there aren't any closed tabs to be shown. r=liuche draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 28 May 2016 20:06:08 +0200
changeset 373341 b43299782de577a87ac33a58d6eb9a64cb9a0bc7
parent 373340 9ed35bfde016667b5a05a5b5699b4233c6601add
child 373342 9b3316ad386c19767f5deaa4cbab8da89c9b298f
push id19745
push usermozilla@buttercookie.de
push dateTue, 31 May 2016 17:03:29 +0000
reviewersliuche
bugs1251362
milestone49.0a1
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
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
--- 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;