Make folder collapse respect folder heirarchy. (#669)

This commit is contained in:
dosiecki 2015-03-31 02:46:49 -07:00
parent 0be7086ffb
commit d19d184c5f
2 changed files with 120 additions and 24 deletions

View file

@ -3,9 +3,11 @@ package com.newsblur.database;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import android.app.Activity;
@ -50,12 +52,14 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
private Cursor socialFeedCursor;
private Map<String,Folder> folders = Collections.emptyMap();
private Map<String,Folder> flatFolders = Collections.emptyMap();
private List<String> activeFolderNames;
private List<List<Feed>> activeFolderChildren;
private List<Integer> neutCounts;
private List<Integer> posCounts;
private Map<String,Feed> feeds = Collections.emptyMap();
private int savedStoriesCount;
private Set<String> closedFolders = new HashSet<String>();
private Context context;
@ -130,14 +134,15 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
if (convertView == null) {
v = inflater.inflate((isExpanded) ? R.layout.row_folder_collapsed : R.layout.row_folder_collapsed, parent, false);
}
final String folderName = activeFolderNames.get(convertGroupPositionToActiveFolderIndex(groupPosition));
String folderName = activeFolderNames.get(convertGroupPositionToActiveFolderIndex(groupPosition));
TextView folderTitle = ((TextView) v.findViewById(R.id.row_foldername));
folderTitle.setText(folderName.toUpperCase());
final String canonicalFolderName = flatFolders.get(folderName).name;
folderTitle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(v.getContext(), FolderItemsList.class);
i.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, folderName);
i.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, canonicalFolderName);
i.putExtra(FolderItemsList.EXTRA_STATE, currentState);
((Activity) context).startActivity(i);
}
@ -216,10 +221,10 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
* canonical name of the folder, not the flattened heirachial name.
*/
@Override
public String getGroup(int groupPosition) {
public synchronized String getGroup(int groupPosition) {
int activeFolderIndex = convertGroupPositionToActiveFolderIndex(groupPosition);
String flatFolderName = activeFolderNames.get(activeFolderIndex);
Folder folder = folders.get(flatFolderName);
Folder folder = flatFolders.get(flatFolderName);
return folder.name;
}
@ -230,7 +235,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
@Override
public int getGroupCount() {
public synchronized int getGroupCount() {
// in addition to the real folders returned by the /reader/feeds API, there are virtual folders
// for global shared stories, social feeds and saved stories
if (activeFolderNames == null) return 0;
@ -238,7 +243,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
@Override
public long getGroupId(int groupPosition) {
public synchronized long getGroupId(int groupPosition) {
// Global shared, all shared and saved stories don't have IDs so give them a really
// huge one.
if (groupPosition == GLOBAL_SHARED_STORIES_GROUP_POSITION) {
@ -253,7 +258,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
@Override
public int getChildrenCount(int groupPosition) {
public synchronized int getChildrenCount(int groupPosition) {
if (groupPosition == ALL_SHARED_STORIES_GROUP_POSITION) {
if (socialFeedCursor == null) return 0;
return socialFeedCursor.getCount();
@ -265,7 +270,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
@Override
public String getChild(int groupPosition, int childPosition) {
public synchronized String getChild(int groupPosition, int childPosition) {
if (groupPosition == ALL_SHARED_STORIES_GROUP_POSITION) {
socialFeedCursor.moveToPosition(childPosition);
return getStr(socialFeedCursor, DatabaseConstants.SOCIAL_FEED_ID);
@ -275,11 +280,11 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
@Override
public long getChildId(int groupPosition, int childPosition) {
public synchronized long getChildId(int groupPosition, int childPosition) {
return getChild(groupPosition, childPosition).hashCode();
}
public String getGroupName(int groupPosition) {
public synchronized String getGroupUniqueName(int groupPosition) {
// these "names" aren't actually what is used to render the row, but are used
// by the fragment for tracking row identity to save open/close preferences
if (groupPosition == ALL_SHARED_STORIES_GROUP_POSITION) {
@ -299,7 +304,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
* specific name in the DB so we can find it.
*/
public boolean isFolderRoot(int groupPosition) {
return ( getGroupName(groupPosition).equals(AppConstants.ROOT_FOLDER) );
return ( getGroupUniqueName(groupPosition).equals(AppConstants.ROOT_FOLDER) );
}
/**
@ -319,9 +324,11 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
public synchronized void setFoldersCursor(Cursor cursor) {
if ((cursor.getCount() < 1) || (!cursor.isBeforeFirst())) return;
folders = new LinkedHashMap<String,Folder>(cursor.getCount());
flatFolders = new LinkedHashMap<String,Folder>(cursor.getCount());
while (cursor.moveToNext()) {
Folder folder = Folder.fromCursor(cursor);
folders.put(folder.flatName(), folder);
folders.put(folder.name, folder);
flatFolders.put(folder.flatName(), folder);
}
recountFeeds();
notifyDataSetChanged();
@ -354,16 +361,22 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
neutCounts = new ArrayList<Integer>();
posCounts = new ArrayList<Integer>();
// create a sorted list of folder display names
List<String> sortedFolderNames = new ArrayList<String>(folders.keySet());
List<String> sortedFolderNames = new ArrayList<String>(flatFolders.keySet());
customSortList(sortedFolderNames);
// figure out which sub-folders are hidden because their parents are closed (flat names)
Set<String> hiddenSubFolders = getSubFoldersRecursive(closedFolders);
Set<String> hiddenSubFoldersFlat = new HashSet<String>(hiddenSubFolders.size());
for (String hiddenSub : hiddenSubFolders) hiddenSubFoldersFlat.add(folders.get(hiddenSub).flatName());
// inspect folders to see if the are active for display
for (String folderName : sortedFolderNames) {
if (hiddenSubFoldersFlat.contains(folderName)) continue;
List<Feed> activeFeeds = new ArrayList<Feed>();
int neutCount = 0;
int posCount = 0;
for (String feedId : folders.get(folderName).feedIds) {
for (String feedId : flatFolders.get(folderName).feedIds) {
Feed f = feeds.get(feedId);
if (f != null) {
// activeFeeds is a list, so it doesn't handle duplication (which the API allows) gracefully
if ((f != null) &&(!activeFeeds.contains(f))) {
if (((currentState == StateFilter.BEST) && (f.positiveCount > 0)) ||
((currentState == StateFilter.SOME) && ((f.positiveCount + f.neutralCount > 0))) ||
(currentState == StateFilter.ALL)) {
@ -383,6 +396,29 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
}
/**
* Given a set of (not-flat) folder names, figure out child folder names (also not flat). Does
* not include the initially passed folder names.
*/
private Set<String> getSubFoldersRecursive(Set<String> parentFolders) {
HashSet<String> subFolders = new HashSet<String>();
outerloop: for (String folder : parentFolders) {
Folder f = folders.get(folder);
if (f == null) continue;
innerloop: for (String child : f.children) {
if (parentFolders.contains(child)) continue innerloop;
subFolders.add(child);
}
subFolders.addAll(getSubFoldersRecursive(subFolders));
}
return subFolders;
}
public synchronized void forceRecount() {
recountFeeds();
notifyDataSetChanged();
}
public Feed getFeed(String feedId) {
return feeds.get(feedId);
}
@ -399,6 +435,20 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
currentState = state;
}
/**
* Indicates that a folder is closed or not, so we can correctly display (or not) sub-folders.
*/
public void setFolderClosed(String folderName, boolean closed) {
// we get a flat name, but need to use a canonical name internally
Folder folder = flatFolders.get(folderName);
if (folder == null) return; // beat the cursors
if (closed) {
closedFolders.add(folder.name);
} else {
closedFolders.remove(folder.name);
}
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;

View file

@ -41,7 +41,12 @@ import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
public class FolderListFragment extends NbFragment implements OnGroupClickListener, OnChildClickListener, OnCreateContextMenuListener, LoaderManager.LoaderCallbacks<Cursor> {
public class FolderListFragment extends NbFragment implements OnGroupClickListener,
OnChildClickListener,
OnCreateContextMenuListener,
LoaderManager.LoaderCallbacks<Cursor>,
ExpandableListView.OnGroupCollapseListener,
ExpandableListView.OnGroupExpandListener {
private static final int SOCIALFEEDS_LOADER = 1;
private static final int FOLDERS_LOADER = 2;
@ -146,6 +151,8 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
list.setAdapter(adapter);
list.setOnGroupClickListener(this);
list.setOnChildClickListener(this);
list.setOnGroupCollapseListener(this);
list.setOnGroupExpandListener(this);
// Main activity needs to listen for scrolls to prevent refresh from firing unnecessarily
list.setOnScrollListener((android.widget.AbsListView.OnScrollListener) getActivity());
@ -153,18 +160,27 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
return v;
}
public void checkOpenFolderPreferences() {
/**
* Check the open/closed status of folders against what we have stored in the prefs
* database. The list widget likes to default all folders to closed, so open them up
* unless expressly collapsed at some point.
*/
private void checkOpenFolderPreferences() {
// make sure we didn't beat construction
if ((this.list == null) || (this.sharedPreferences == null)) return;
for (int i = 0; i < adapter.getGroupCount(); i++) {
String groupName = adapter.getGroupName(i);
if (sharedPreferences.getBoolean(AppConstants.FOLDER_PRE + "_" + groupName, true)) {
this.list.expandGroup(i);
String flatGroupName = adapter.getGroupUniqueName(i);
if (sharedPreferences.getBoolean(AppConstants.FOLDER_PRE + "_" + flatGroupName, true)) {
if (list.isGroupExpanded(i) == false) list.expandGroup(i);
adapter.setFolderClosed(flatGroupName, false);
} else {
this.list.collapseGroup(i);
if (list.isGroupExpanded(i) == true) list.collapseGroup(i);
adapter.setFolderClosed(flatGroupName, true);
}
}
// we might have just initialised the closed set of folders in the adapter
adapter.forceRecount();
}
@Override
@ -244,20 +260,50 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
startActivity(i);
return true;
} else {
// the intents started by clicking on folder group names are handled in the Adapter, this
// just handles clicks on the expandos
if ((group != null) && (group.findViewById(R.id.row_foldersums) != null)) {
String groupName = adapter.getGroupName(groupPosition);
// the isGroupExpanded() call reflects the state of the group before this click
if (list.isGroupExpanded(groupPosition)) {
group.findViewById(R.id.row_foldersums).setVisibility(View.VISIBLE);
sharedPreferences.edit().putBoolean(AppConstants.FOLDER_PRE + "_" + groupName, false).commit();
} else {
group.findViewById(R.id.row_foldersums).setVisibility(View.INVISIBLE);
sharedPreferences.edit().putBoolean(AppConstants.FOLDER_PRE + "_" + groupName, true).commit();
}
}
return false;
}
}
@Override
public void onGroupExpand(int groupPosition) {
// these shouldn't ever be collapsible
if (adapter.isFolderRoot(groupPosition)) return;
if (adapter.isRowSavedStories(groupPosition)) return;
String flatGroupName = adapter.getGroupUniqueName(groupPosition);
// save the expanded preference, since the widget likes to forget it
sharedPreferences.edit().putBoolean(AppConstants.FOLDER_PRE + "_" + flatGroupName, true).commit();
// trigger display/hide of sub-folders
adapter.setFolderClosed(flatGroupName, false);
adapter.forceRecount();
// re-check open/closed state of sub folders, since the list will have forgot them
checkOpenFolderPreferences();
}
@Override
public void onGroupCollapse(int groupPosition) {
// these shouldn't ever be collapsible
if (adapter.isFolderRoot(groupPosition)) return;
if (adapter.isRowSavedStories(groupPosition)) return;
String flatGroupName = adapter.getGroupUniqueName(groupPosition);
// save the collapsed preference, since the widget likes to forget it
sharedPreferences.edit().putBoolean(AppConstants.FOLDER_PRE + "_" + flatGroupName, false).commit();
// trigger display/hide of sub-folders
adapter.setFolderClosed(flatGroupName, true);
adapter.forceRecount();
}
@Override
public boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
String childName = adapter.getChild(groupPosition, childPosition);