Group list headers and selection
- Sort groups according to account
- Add headers to groups in same account with # of groups in that account
- Add "selection" background on list item in tablet, but disable it on
the phone
- Misc: enable fast scroll, move icon to right
Change-Id: I1c83aa686de2431b3483a1591ecad7e9e6893cdc
diff --git a/res/layout/group_browse_list_fragment.xml b/res/layout/group_browse_list_fragment.xml
index 50c02c8..d41772d 100644
--- a/res/layout/group_browse_list_fragment.xml
+++ b/res/layout/group_browse_list_fragment.xml
@@ -26,7 +26,9 @@
android:layout_height="0dip"
android:fastScrollEnabled="true"
android:scrollbarStyle="outsideOverlay"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:cacheColorHint="@android:color/transparent"
+ android:divider="@null" />
<TextView
android:id="@+id/empty"
diff --git a/res/layout/group_browse_list_item.xml b/res/layout/group_browse_list_item.xml
index 28f4e17..d94d444 100644
--- a/res/layout/group_browse_list_item.xml
+++ b/res/layout/group_browse_list_item.xml
@@ -19,24 +19,15 @@
class="com.android.contacts.group.GroupBrowseListAdapter$GroupListItem"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/icon"
- android:scaleType="center"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="10dip"
- android:layout_gravity="center_vertical"
- android:src="@drawable/ic_menu_display_all_holo_light" />
+ android:layout_height="wrap_content"
+ style="@style/GroupBrowseListItem">
<LinearLayout
android:orientation="vertical"
- android:layout_width="wrap_content"
+ android:layout_width="0dip"
android:layout_height="match_parent"
- android:paddingTop="5dip"
- android:paddingBottom="5dip">
+ android:layout_weight="1"
+ android:padding="5dip">
<TextView
android:id="@+id/label"
@@ -60,4 +51,13 @@
</LinearLayout>
+ <ImageView
+ android:id="@+id/icon"
+ android:scaleType="center"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="20dip"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/ic_menu_display_all_holo_light" />
+
</view>
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
index 0179f6a..f73e51d 100644
--- a/res/values-xlarge/styles.xml
+++ b/res/values-xlarge/styles.xml
@@ -116,4 +116,8 @@
<item name="android:gravity">center_vertical</item>
<item name="android:paddingTop">5dip</item>
</style>
+
+ <style name="GroupBrowseListItem">
+ <item name="android:background">@drawable/list_item_activated_background</item>
+ </style>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 77863d5..52facac 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1497,6 +1497,12 @@
<!-- Title of the dialog that allows deletion of a contact group [CHAR LIMIT=128] -->
<string name="delete_group_dialog_title">Delete group</string>
+ <!-- Shows how many groups are from the specified account [CHAR LIMIT=15] -->
+ <plurals name="num_groups_in_account">
+ <item quantity="one">1 group</item>
+ <item quantity="other"><xliff:g id="count">%0$d</xliff:g> groups</item>
+ </plurals>
+
<!-- Confirmation message of the dialog that allows deletion of a contact group [CHAR LIMIT=256] -->
<string name="delete_group_dialog_message">Are you sure you want to delete the group
\'<xliff:g id="group_label" example="Friends">%1$s</xliff:g>\'?
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f18a340..509180e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -322,4 +322,8 @@
<item name="android:padding">5dip</item>
<item name="android:background">@drawable/list_selector</item>
</style>
+
+ <style name="GroupBrowseListItem">
+ <item name="android:paddingRight">20dip</item>
+ </style>
</resources>
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 502c31d..4b0f0e4 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -408,13 +408,14 @@
}
mListFragment.setContactsRequest(mRequest);
- configureListFragmentForRequest();
+ configureContactListFragmentForRequest();
} else {
mSearchMode = mActionBarAdapter.isSearchMode();
}
- configureListFragment();
+ configureContactListFragment();
+ configureGroupListFragment();
invalidateOptionsMenu();
}
@@ -478,7 +479,7 @@
}
}
- private void configureListFragmentForRequest() {
+ private void configureContactListFragmentForRequest() {
Uri contactUri = mRequest.getContactUri();
if (contactUri != null) {
mListFragment.setSelectedContactUri(contactUri);
@@ -498,7 +499,7 @@
}
}
- private void configureListFragment() {
+ private void configureContactListFragment() {
mListFragment.setSearchMode(mSearchMode);
mListFragment.setVisibleScrollbarEnabled(!mSearchMode);
@@ -510,6 +511,14 @@
mListFragment.setQuickContactEnabled(!mContentPaneDisplayed);
}
+ private void configureGroupListFragment() {
+ mGroupsFragment.setVerticalScrollbarPosition(
+ mContentPaneDisplayed
+ ? View.SCROLLBAR_POSITION_LEFT
+ : View.SCROLLBAR_POSITION_RIGHT);
+ mGroupsFragment.setSelectionVisible(mContentPaneDisplayed);
+ }
+
@Override
public void onProviderStatusChange() {
updateFragmentVisibility();
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
index c17fc08..b836ab9 100644
--- a/src/com/android/contacts/group/GroupBrowseListAdapter.java
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -18,6 +18,7 @@
import com.android.contacts.GroupMetaData;
import com.android.contacts.R;
+import com.android.contacts.list.ContactListPinnedHeaderView;
import android.content.ContentUris;
import android.content.Context;
@@ -31,19 +32,55 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Adapter to populate the list of groups.
*/
public class GroupBrowseListAdapter extends BaseAdapter {
+ private Context mContext;
private LayoutInflater mLayoutInflater;
- private List<GroupMetaData> mGroupList;
- public GroupBrowseListAdapter(Context context, List<GroupMetaData> groupList) {
+ private List<GroupListEntry> mGroupList = new ArrayList<GroupListEntry>();
+ private boolean mSelectionVisible;
+ private Uri mSelectedGroupUri;
+
+ enum ViewType {
+ HEADER, ITEM;
+ }
+
+ private static final int VIEW_TYPE_COUNT = ViewType.values().length;
+
+ public GroupBrowseListAdapter(Context context, Map<String, List<GroupMetaData>> groupMap) {
+ mContext = context;
mLayoutInflater = LayoutInflater.from(context);
- mGroupList = groupList;
+ for (String accountName : groupMap.keySet()) {
+ List<GroupMetaData> groupsListForAccount = groupMap.get(accountName);
+
+ // Add account name as header for section
+ mGroupList.add(GroupListEntry.createEntryForHeader(accountName,
+ groupsListForAccount.size()));
+
+ // Add groups within that account as subsequent list items.
+ for (GroupMetaData singleGroup : groupsListForAccount) {
+ mGroupList.add(GroupListEntry.createEntryForGroup(singleGroup));
+ }
+ }
+ }
+
+ public void setSelectionVisible(boolean flag) {
+ mSelectionVisible = flag;
+ }
+
+ public void setSelectedGroup(Uri groupUri) {
+ mSelectedGroupUri = groupUri;
+ }
+
+ private boolean isSelectedGroup(Uri groupUri) {
+ return mSelectedGroupUri != null && mSelectedGroupUri.equals(groupUri);
}
@Override
@@ -53,24 +90,109 @@
@Override
public long getItemId(int position) {
- return getItem(position).getGroupId();
+ return mGroupList.get(position).id;
}
@Override
- public GroupMetaData getItem(int position) {
+ public GroupListEntry getItem(int position) {
return mGroupList.get(position);
}
@Override
+ public int getItemViewType(int position) {
+ return mGroupList.get(position).type.ordinal();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return VIEW_TYPE_COUNT;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return mGroupList.get(position).type == ViewType.ITEM;
+ }
+
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
+ GroupListEntry item = getItem(position);
+ switch (item.type) {
+ case HEADER:
+ return getHeaderView(item, convertView, parent);
+ case ITEM:
+ return getGroupListItemView(item, convertView, parent);
+ default:
+ throw new IllegalStateException("Invalid GroupListEntry item type " + item.type);
+ }
+
+ }
+
+ private View getHeaderView(GroupListEntry entry, View convertView, ViewGroup parent) {
+ ContactListPinnedHeaderView result = (ContactListPinnedHeaderView) (convertView == null ?
+ new ContactListPinnedHeaderView(mContext, null) :
+ convertView);
+ String groupCountString = mContext.getResources().getQuantityString(
+ R.plurals.num_groups_in_account, entry.count, entry.count);
+ // TODO: Format this correctly by using 2 TextViews when the
+ // ContactListPinnedHeaderView is refactored.
+ result.setSectionHeader(entry.title + " " + groupCountString);
+ return result;
+ }
+
+ private View getGroupListItemView(GroupListEntry entry, View convertView, ViewGroup parent) {
GroupListItem result = (GroupListItem) (convertView == null ?
mLayoutInflater.inflate(R.layout.group_browse_list_item, parent, false) :
convertView);
- result.loadFromGroup(getItem(position));
+ result.loadFromGroup(entry.groupData);
+ if (mSelectionVisible) {
+ result.setActivated(isSelectedGroup(result.getUri()));
+ }
return result;
}
/**
+ * This is a data model object to represent one row in the list of groups were the entry
+ * could be a header or group item.
+ */
+ public static class GroupListEntry {
+ public final ViewType type;
+ public final String title;
+ public final int count;
+ public final GroupMetaData groupData;
+ /**
+ * The id is equal to the group ID (if groupData is available), otherwise it is -1 for
+ * header entries.
+ */
+ public final long id;
+
+ private GroupListEntry(ViewType entryType, String headerTitle, int headerGroupCount,
+ GroupMetaData groupMetaData, long entryId) {
+ type = entryType;
+ title = headerTitle;
+ count = headerGroupCount;
+ groupData = groupMetaData;
+ id = entryId;
+ }
+
+ public static GroupListEntry createEntryForHeader(String headerTitle, int groupCount) {
+ return new GroupListEntry(ViewType.HEADER, headerTitle, groupCount, null, -1);
+ }
+
+ public static GroupListEntry createEntryForGroup(GroupMetaData groupMetaData) {
+ if (groupMetaData == null) {
+ throw new IllegalStateException("Cannot create list entry for a hull group");
+ }
+ return new GroupListEntry(ViewType.ITEM, null, 0, groupMetaData,
+ groupMetaData.getGroupId());
+ }
+ }
+
+ /**
* A row in a list of groups, where this row displays a single group's title
* and associated account.
*/
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 150a00f..958d9f8 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -44,7 +44,9 @@
import android.widget.ListView;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Fragment to display the list of groups.
@@ -71,12 +73,24 @@
private Context mContext;
private Cursor mGroupListCursor;
- private List<GroupMetaData> mGroupList = new ArrayList<GroupMetaData>();
+
+ /**
+ * Map of account name to a list of {@link GroupMetaData} objects
+ * representing groups within that account.
+ * TODO: Change account name string into a wrapper object that has
+ * account name, type, and authority.
+ */
+ private Map<String, List<GroupMetaData>> mGroupMap = new HashMap<String, List<GroupMetaData>>();
private View mRootView;
private ListView mListView;
private View mEmptyView;
+ private GroupBrowseListAdapter mAdapter;
+ private boolean mSelectionVisible;
+
+ private int mVerticalScrollbarPosition = View.SCROLLBAR_POSITION_RIGHT;
+
private OnGroupBrowserActionListener mListener;
public GroupBrowseListFragment() {
@@ -93,6 +107,31 @@
return mRootView;
}
+ public void setVerticalScrollbarPosition(int position) {
+ if (mVerticalScrollbarPosition != position) {
+ mVerticalScrollbarPosition = position;
+ configureVerticalScrollbar();
+ }
+ }
+
+ private void configureVerticalScrollbar() {
+ mListView.setFastScrollEnabled(true);
+ mListView.setFastScrollAlwaysVisible(true);
+ mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition);
+ mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+ int leftPadding = 0;
+ int rightPadding = 0;
+ if (mVerticalScrollbarPosition == View.SCROLLBAR_POSITION_LEFT) {
+ leftPadding = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.list_visible_scrollbar_padding);
+ } else {
+ rightPadding = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.list_visible_scrollbar_padding);
+ }
+ mListView.setPadding(leftPadding, mListView.getPaddingTop(),
+ rightPadding, mListView.getPaddingBottom());
+ }
+
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
@@ -136,7 +175,7 @@
if (mGroupListCursor == null) {
return;
}
- mGroupList.clear();
+ mGroupMap.clear();
mGroupListCursor.moveToPosition(-1);
while (mGroupListCursor.moveToNext()) {
String accountName = mGroupListCursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
@@ -150,12 +189,24 @@
? false
: mGroupListCursor.getInt(GroupMetaDataLoader.FAVORITES) != 0;
- // TODO: Separate groups according to account name and type.
- mGroupList.add(new GroupMetaData(
- accountName, accountType, groupId, title, defaultGroup, favorites));
+ GroupMetaData newGroup = new GroupMetaData(accountName, accountType, groupId, title,
+ defaultGroup, favorites);
+
+ if (mGroupMap.containsKey(accountName)) {
+ List<GroupMetaData> groups = mGroupMap.get(accountName);
+ groups.add(newGroup);
+ } else {
+ List<GroupMetaData> groups = new ArrayList<GroupMetaData>();
+ groups.add(newGroup);
+ mGroupMap.put(accountName, groups);
+ }
+
}
- mListView.setAdapter(new GroupBrowseListAdapter(mContext, mGroupList));
+ mAdapter = new GroupBrowseListAdapter(mContext, mGroupMap);
+ mAdapter.setSelectionVisible(mSelectionVisible);
+
+ mListView.setAdapter(mAdapter);
mListView.setEmptyView(mEmptyView);
mListView.setOnItemClickListener(new OnItemClickListener() {
@Override
@@ -170,7 +221,17 @@
mListener = listener;
}
+ public void setSelectionVisible(boolean flag) {
+ mSelectionVisible = flag;
+ }
+
+ private void setSelectedGroup(Uri groupUri) {
+ mAdapter.setSelectedGroup(groupUri);
+ mListView.invalidateViews();
+ }
+
private void viewGroup(Uri groupUri) {
+ setSelectedGroup(groupUri);
if (mListener != null) mListener.onViewGroupAction(groupUri);
}