Sectioned headers in the assignment picker. booyah

pull/14/head
Sam Bosley 14 years ago
parent e92c2ef048
commit 197fe02be4

@ -30,5 +30,6 @@
<classpathentry exported="true" kind="lib" path="libs/gson-1.7.1.jar"/>
<classpathentry kind="lib" path="libs/crittercism_v2_1_2.jar"/>
<classpathentry kind="lib" path="libs/findbugs-annotations.jar"/>
<classpathentry kind="lib" path="libs/CWAC-SackOfViewsAdapter.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

@ -40,6 +40,7 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.commonsware.cwac.merge.MergeAdapter;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
@ -114,8 +115,6 @@ public class EditPeopleControlSet extends PopupControlSet {
private final View assignedClear;
private final ArrayList<AssignedToUser> listValues = new ArrayList<AssignedToUser>();
private final int loginRequestCode;
private boolean assignedToMe = false;
@ -351,16 +350,17 @@ public class EditPeopleControlSet extends PopupControlSet {
HashSet<String> emails = new HashSet<String>();
HashMap<String, AssignedToUser> names = new HashMap<String, AssignedToUser>();
ArrayList<AssignedToUser> coreUsers = new ArrayList<AssignedToUser>();
ArrayList<AssignedToUser> listUsers = new ArrayList<AssignedToUser>();
ArrayList<AssignedToUser> astridUsers = new ArrayList<AssignedToUser>();
int assignedIndex = 0;
try {
if(t.getValue(Task.USER_ID) > 0) {
JSONObject user = new JSONObject(t.getValue(Task.USER));
sharedPeople.add(0, user);
}
ArrayList<JSONObject> coreUsersJson = new ArrayList<JSONObject>();
JSONObject myself = new JSONObject();
myself.put("id", Task.USER_ID_SELF);
sharedPeople.add(0, myself);
myself.put("picture", ActFmPreferenceService.thisUser().optString("picture"));
coreUsersJson.add(myself);
boolean hasTags = t.getTransitory("tags") != null &&
((HashSet<String>)t.getTransitory("tags")).size() > 0;
@ -368,106 +368,154 @@ public class EditPeopleControlSet extends PopupControlSet {
if (addUnassigned) {
JSONObject unassigned = new JSONObject();
unassigned.put("id", Task.USER_ID_UNASSIGNED);
sharedPeople.add(1, unassigned);
unassigned.put("default_picture", R.drawable.icn_anyone);
coreUsersJson.add(unassigned);
}
addAstridFriends(sharedPeople);
// de-duplicate by user id and/or email
listValues.clear();
for(int i = 0; i < sharedPeople.size(); i++) {
JSONObject person = sharedPeople.get(i);
if(person == null)
continue;
long id = person.optLong("id", -2);
if(id == ActFmPreferenceService.userId() || (id >= -1 && userIds.contains(id)))
continue;
userIds.add(id);
String email = person.optString("email");
if(!TextUtils.isEmpty(email) && emails.contains(email))
continue;
emails.add(email);
String name = person.optString("name");
if(id == 0)
name = activity.getString(R.string.actfm_EPA_assign_me);
if (id == -1)
name = activity.getString(R.string.actfm_EPA_unassigned);
AssignedToUser atu = new AssignedToUser(name, person);
listValues.add(atu);
if(names.containsKey(name)) {
AssignedToUser user = names.get(name);
if(user != null && user.user.has("email")) {
user.label += " (" + user.user.optString("email") + ")";
names.put(name, null);
}
if(!TextUtils.isEmpty("email"))
atu.label += " (" + email + ")";
} else if(TextUtils.isEmpty(name)) {
if(!TextUtils.isEmpty("email"))
atu.label = email;
else
listValues.remove(atu);
} else
names.put(name, atu);
if(t.getValue(Task.USER_ID) > 0) {
JSONObject user = new JSONObject(t.getValue(Task.USER));
coreUsersJson.add(0, user);
}
String assignedStr = t.getValue(Task.USER);
if (!TextUtils.isEmpty(assignedStr)) {
JSONObject assigned = new JSONObject(assignedStr);
long assignedId = assigned.optLong("id", -2);
String assignedEmail = assigned.optString("email");
for (int i = 0; i < listValues.size(); i++) {
JSONObject user = listValues.get(i).user;
if (user != null) {
if (user.optLong("id") == assignedId ||
(user.optString("email").equals(assignedEmail) &&
!(TextUtils.isEmpty(assignedEmail))))
assignedIndex = i;
}
}
}
ArrayList<JSONObject> astridFriends = getAstridFriends();
// de-duplicate by user id and/or email
coreUsers = convertJsonUsersToAssignedUsers(coreUsersJson, userIds, emails, names);
listUsers = convertJsonUsersToAssignedUsers(sharedPeople, userIds, emails, names);
astridUsers = convertJsonUsersToAssignedUsers(astridFriends, userIds, emails, names);
contactPickerUser = new AssignedToUser(activity.getString(R.string.actfm_EPA_choose_contact),
new JSONObject().put("default_picture", R.drawable.icn_friends)
.put(CONTACT_CHOOSER_USER, true));
.put(CONTACT_CHOOSER_USER, true));
int contactsIndex = addUnassigned ? 2 : 1;
listValues.add(contactsIndex, contactPickerUser);
if (assignedIndex >= contactsIndex)
assignedIndex++;
coreUsers.add(contactsIndex, contactPickerUser);
for (AssignedChangedListener l : listeners) {
if (l.shouldShowTaskRabbit()) {
taskRabbitUser = new AssignedToUser(activity.getString(R.string.actfm_EPA_task_rabbit), new JSONObject().put("default_picture", R.drawable.task_rabbit_image));
int taskRabbitIndex = addUnassigned ? 3 : 2;
listValues.add(taskRabbitIndex, taskRabbitUser);
coreUsers.add(taskRabbitIndex, taskRabbitUser);
if(l.didPostToTaskRabbit()){
assignedIndex = taskRabbitIndex;
} else if (assignedIndex >= taskRabbitIndex) {
assignedIndex++;
}
}
}
if (assignedIndex == 0) {
assignedIndex = findAssignedIndex(t, coreUsers, listUsers, astridUsers);
}
} catch (JSONException e) {
exceptionService.reportError("json-reading-data", e);
}
selected = assignedIndex;
final AssignedUserAdapter usersAdapter = new AssignedUserAdapter(activity, listValues);
final MergeAdapter mergeAdapter = new MergeAdapter();
AssignedUserAdapter coreUserAdapter = new AssignedUserAdapter(activity, coreUsers, 0);
AssignedUserAdapter listUserAdapter = new AssignedUserAdapter(activity, listUsers, coreUserAdapter.getCount() + 1);
int offsetForAstridUsers = listUserAdapter.getCount() > 0 ? 2 : 1;
AssignedUserAdapter astridUserAdapter = new AssignedUserAdapter(activity, astridUsers, coreUserAdapter.getCount() + listUserAdapter.getCount() + offsetForAstridUsers);
LayoutInflater inflater = activity.getLayoutInflater();
TextView header1 = (TextView) inflater.inflate(R.layout.list_header, null);
header1.setText(R.string.actfm_EPA_assign_header_members);
TextView header2 = (TextView) inflater.inflate(R.layout.list_header, null);
header2.setText(R.string.actfm_EPA_assign_header_friends);
mergeAdapter.addAdapter(coreUserAdapter);
if (listUserAdapter.getCount() > 0) {
mergeAdapter.addView(header1);
mergeAdapter.addAdapter(listUserAdapter);
}
if (astridUserAdapter.getCount() > 0) {
mergeAdapter.addView(header2);
mergeAdapter.addAdapter(astridUserAdapter);
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
assignedList.setAdapter(usersAdapter);
assignedList.setAdapter(mergeAdapter);
assignedList.setItemChecked(selected, true);
refreshDisplayView();
}
});
}
private void addAstridFriends(ArrayList<JSONObject> sharedPeople) {
@SuppressWarnings("nls")
private ArrayList<AssignedToUser> convertJsonUsersToAssignedUsers(ArrayList<JSONObject> jsonUsers,
HashSet<Long> userIds, HashSet<String> emails, HashMap<String, AssignedToUser> names) {
ArrayList<AssignedToUser> users = new ArrayList<AssignedToUser>();
for(int i = 0; i < jsonUsers.size(); i++) {
JSONObject person = jsonUsers.get(i);
if(person == null)
continue;
long id = person.optLong("id", -2);
if(id == ActFmPreferenceService.userId() || (id >= -1 && userIds.contains(id)))
continue;
userIds.add(id);
String email = person.optString("email");
if(!TextUtils.isEmpty(email) && emails.contains(email))
continue;
emails.add(email);
String name = person.optString("name");
if(id == 0)
name = activity.getString(R.string.actfm_EPA_assign_me);
if (id == -1)
name = activity.getString(R.string.actfm_EPA_unassigned);
AssignedToUser atu = new AssignedToUser(name, person);
users.add(atu);
if(names.containsKey(name)) {
AssignedToUser user = names.get(name);
if(user != null && user.user.has("email")) {
user.label += " (" + user.user.optString("email") + ")";
names.put(name, null);
}
if(!TextUtils.isEmpty("email"))
atu.label += " (" + email + ")";
} else if(TextUtils.isEmpty(name)) {
if(!TextUtils.isEmpty("email"))
atu.label = email;
else
users.remove(atu);
} else
names.put(name, atu);
}
return users;
}
@SuppressWarnings("nls")
private int findAssignedIndex(Task t, ArrayList<AssignedToUser>... userLists) throws JSONException {
String assignedStr = t.getValue(Task.USER);
if (!TextUtils.isEmpty(assignedStr)) {
JSONObject assigned = new JSONObject(assignedStr);
long assignedId = assigned.optLong("id", -2);
String assignedEmail = assigned.optString("email");
int index = 0;
for (ArrayList<AssignedToUser> userList : userLists) {
for (int i = 0; i < userList.size(); i++) {
JSONObject user = userList.get(i).user;
if (user != null) {
if (user.optLong("id") == assignedId ||
(user.optString("email").equals(assignedEmail) &&
!(TextUtils.isEmpty(assignedEmail))))
return index;
}
index++;
}
index++; // Add one for headers separating user lists
}
}
return 0;
}
private ArrayList<JSONObject> getAstridFriends() {
ArrayList<JSONObject> astridFriends = new ArrayList<JSONObject>();
TodorooCursor<User> users = userDao.query(Query.select(User.PROPERTIES).orderBy(Order.asc(User.NAME)));
try {
User user = new User();
@ -476,7 +524,7 @@ public class EditPeopleControlSet extends PopupControlSet {
JSONObject userJson = new JSONObject();
try {
ActFmSyncService.JsonHelper.jsonFromUser(userJson, user);
sharedPeople.add(userJson);
astridFriends.add(userJson);
} catch (JSONException e) {
// Ignored
}
@ -484,14 +532,18 @@ public class EditPeopleControlSet extends PopupControlSet {
} finally {
users.close();
}
return astridFriends;
}
private class AssignedUserAdapter extends ArrayAdapter<AssignedToUser> {
public AssignedUserAdapter(Context context, ArrayList<AssignedToUser> people) {
private final int positionOffset;
public AssignedUserAdapter(Context context, ArrayList<AssignedToUser> people, int positionOffset) {
super(context, R.layout.assigned_adapter_row, people);
this.positionOffset = positionOffset;
}
@SuppressWarnings("nls")
@ -501,21 +553,14 @@ public class EditPeopleControlSet extends PopupControlSet {
convertView = activity.getLayoutInflater().inflate(R.layout.assigned_adapter_row, parent, false);
CheckedTextView ctv = (CheckedTextView) convertView.findViewById(android.R.id.text1);
super.getView(position, ctv, parent);
if (assignedList.getCheckedItemPosition() == position) {
if (assignedList.getCheckedItemPosition() == position + positionOffset) {
ctv.setChecked(true);
} else {
ctv.setChecked(false);
}
AsyncImageView image = (AsyncImageView) convertView.findViewById(R.id.person_image);
image.setDefaultImageResource(R.drawable.icn_default_person_image);
if (position == 0) {
image.setUrl(ActFmPreferenceService.thisUser().optString("picture"));
} else if (position == 1) {
image.setUrl("");
image.setDefaultImageResource(R.drawable.icn_anyone);
} else {
image.setUrl(getItem(position).user.optString("picture"));
}
image.setUrl(getItem(position).user.optString("picture"));
if (getItem(position).user.optInt("default_picture", 0) > 0) {
image.setDefaultImageResource(getItem(position).user.optInt("default_picture"));
}

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_header_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="2dip"
android:paddingBottom="2dip"
android:paddingLeft="7dip"
style="@style/TEA_list_header" />

@ -169,6 +169,12 @@
<!-- task sharing dialog: message body -->
<string name="actfm_EPA_message_body">Help me get this done!</string>
<!-- task sharing dialog: list members section header -->
<string name="actfm_EPA_assign_header_members">List Members</string>
<!-- task sharing dialog: astrid friends section header -->
<string name="actfm_EPA_assign_header_friends">Astrid Friends</string>
<!-- task sharing dialog: message hint -->
<string name="actfm_EPA_tag_label">Create a shared tag?</string>
@ -199,6 +205,7 @@
<string name="actfm_EPA_login_button">Log in</string>
<string name="actfm_EPA_dont_share_button">Make private</string>
<!-- ========================================= sharing login activity == -->

@ -329,6 +329,13 @@
<item name="android:textStyle">bold</item>
<item name="android:textColor">?attr/asTextColor</item>
</style>
<style name="TEA_list_header">
<item name="android:textColor">?attr/asTextColor</item>
<item name="android:textSize">18sp</item>
<item name="android:textStyle">bold</item>
<item name="android:background">#aaaaaaaa</item>
</style>
<!-- ==================================================== TaskAdapter == -->

@ -0,0 +1,379 @@
/***
Copyright (c) 2008-2009 CommonsWare, LLC
Portions (c) 2009 Google, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.commonsware.cwac.merge;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.SectionIndexer;
import java.util.ArrayList;
import java.util.List;
import com.commonsware.cwac.sacklist.SackOfViewsAdapter;
/**
* Adapter that merges multiple child adapters and views
* into a single contiguous whole.
*
* Adapters used as pieces within MergeAdapter must
* have view type IDs monotonically increasing from 0. Ideally,
* adapters also have distinct ranges for their row ids, as
* returned by getItemId().
*
*/
public class MergeAdapter extends BaseAdapter implements SectionIndexer {
protected ArrayList<ListAdapter> pieces=new ArrayList<ListAdapter>();
/**
* Stock constructor, simply chaining to the superclass.
*/
public MergeAdapter() {
super();
}
/**
* Adds a new adapter to the roster of things to appear
* in the aggregate list.
* @param adapter Source for row views for this section
*/
public void addAdapter(ListAdapter adapter) {
pieces.add(adapter);
adapter.registerDataSetObserver(new CascadeDataSetObserver());
}
/**
* Adds a new View to the roster of things to appear
* in the aggregate list.
* @param view Single view to add
*/
public void addView(View view) {
addView(view, false);
}
/**
* Adds a new View to the roster of things to appear
* in the aggregate list.
* @param view Single view to add
* @param enabled false if views are disabled, true if enabled
*/
public void addView(View view, boolean enabled) {
ArrayList<View> list=new ArrayList<View>(1);
list.add(view);
addViews(list, enabled);
}
/**
* Adds a list of views to the roster of things to appear
* in the aggregate list.
* @param views List of views to add
*/
public void addViews(List<View> views) {
addViews(views, false);
}
/**
* Adds a list of views to the roster of things to appear
* in the aggregate list.
* @param views List of views to add
* @param enabled false if views are disabled, true if enabled
*/
public void addViews(List<View> views, boolean enabled) {
if (enabled) {
addAdapter(new EnabledSackAdapter(views));
}
else {
addAdapter(new SackOfViewsAdapter(views));
}
}
/**
* Get the data item associated with the specified
* position in the data set.
* @param position Position of the item whose data we want
*/
@Override
public Object getItem(int position) {
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
return(piece.getItem(position));
}
position-=size;
}
return(null);
}
/**
* Get the adapter associated with the specified
* position in the data set.
* @param position Position of the item whose adapter we want
*/
public ListAdapter getAdapter(int position) {
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
return(piece);
}
position-=size;
}
return(null);
}
/**
* How many items are in the data set represented by this
* Adapter.
*/
@Override
public int getCount() {
int total=0;
for (ListAdapter piece : pieces) {
total+=piece.getCount();
}
return(total);
}
/**
* Returns the number of types of Views that will be
* created by getView().
*/
@Override
public int getViewTypeCount() {
int total=0;
for (ListAdapter piece : pieces) {
total+=piece.getViewTypeCount();
}
return(Math.max(total, 1)); // needed for setListAdapter() before content add'
}
/**
* Get the type of View that will be created by getView()
* for the specified item.
* @param position Position of the item whose data we want
*/
@Override
public int getItemViewType(int position) {
int typeOffset=0;
int result=-1;
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
result=typeOffset+piece.getItemViewType(position);
break;
}
position-=size;
typeOffset+=piece.getViewTypeCount();
}
return(result);
}
/**
* Are all items in this ListAdapter enabled? If yes it
* means all items are selectable and clickable.
*/
@Override
public boolean areAllItemsEnabled() {
return(false);
}
/**
* Returns true if the item at the specified position is
* not a separator.
* @param position Position of the item whose data we want
*/
@Override
public boolean isEnabled(int position) {
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
return(piece.isEnabled(position));
}
position-=size;
}
return(false);
}
/**
* Get a View that displays the data at the specified
* position in the data set.
* @param position Position of the item whose data we want
* @param convertView View to recycle, if not null
* @param parent ViewGroup containing the returned View
*/
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
return(piece.getView(position, convertView, parent));
}
position-=size;
}
return(null);
}
/**
* Get the row id associated with the specified position
* in the list.
* @param position Position of the item whose data we want
*/
@Override
public long getItemId(int position) {
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
return(piece.getItemId(position));
}
position-=size;
}
return(-1);
}
@Override
public int getPositionForSection(int section) {
int position=0;
for (ListAdapter piece : pieces) {
if (piece instanceof SectionIndexer) {
Object[] sections=((SectionIndexer)piece).getSections();
int numSections=0;
if (sections!=null) {
numSections=sections.length;
}
if (section<numSections) {
return(position+((SectionIndexer)piece).getPositionForSection(section));
}
else if (sections!=null) {
section-=numSections;
}
}
position+=piece.getCount();
}
return(0);
}
@Override
public int getSectionForPosition(int position) {
int section=0;
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
if (piece instanceof SectionIndexer) {
return(section+((SectionIndexer)piece).getSectionForPosition(position));
}
return(0);
}
else {
if (piece instanceof SectionIndexer) {
Object[] sections=((SectionIndexer)piece).getSections();
if (sections!=null) {
section+=sections.length;
}
}
}
position-=size;
}
return(0);
}
@Override
public Object[] getSections() {
ArrayList<Object> sections=new ArrayList<Object>();
for (ListAdapter piece : pieces) {
if (piece instanceof SectionIndexer) {
Object[] curSections=((SectionIndexer)piece).getSections();
if (curSections!=null) {
for (Object section : curSections) {
sections.add(section);
}
}
}
}
if (sections.size()==0) {
return(null);
}
return(sections.toArray(new Object[0]));
}
private static class EnabledSackAdapter extends SackOfViewsAdapter {
public EnabledSackAdapter(List<View> views) {
super(views);
}
@Override
public boolean areAllItemsEnabled() {
return(true);
}
@Override
public boolean isEnabled(int position) {
return(true);
}
}
private class CascadeDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
notifyDataSetInvalidated();
}
}
}

@ -0,0 +1,102 @@
package com.commonsware.cwac.merge;
import java.util.List;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.SpinnerAdapter;
/**
* Adapter that merges multiple child adapters into a single
* contiguous whole to be consumed by a Spinner.
*
* Adapters used as pieces within MergeSpinnerAdapter must
* have view type IDs monotonically increasing from 0.
* Ideally, adapters also have distinct ranges for their row
* ids, as returned by getItemId().
*
* All Adapters used as pieces within MergeSpinnerAdapter
* must be properly-configured implementations of
* SpinnerAdapter (e.g., ArrayAdapter, CursorAdapter).
*/
public class MergeSpinnerAdapter extends MergeAdapter {
/**
* Stock constructor, simply chaining to the superclass.
*/
public MergeSpinnerAdapter() {
super();
}
/*
* Returns the drop-down View for a given position, by
* iterating over the pieces. Assumes that all pieces are
* implementations of SpinnerAdapter.
*
* @see android.widget.BaseAdapter#getDropDownView(int,
* android.view.View, android.view.ViewGroup)
*/
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
for (ListAdapter piece : pieces) {
int size=piece.getCount();
if (position<size) {
return(((SpinnerAdapter)piece).getDropDownView(position,
convertView,
parent));
}
position-=size;
}
return(null);
}
/**
* Adds a new View to the roster of things to appear in
* the aggregate list.
*
* @param view
* Single view to add
*/
public void addView(View view) {
throw new RuntimeException("Not supported with MergeSpinnerAdapter");
}
/**
* Adds a new View to the roster of things to appear in
* the aggregate list.
*
* @param view
* Single view to add
* @param enabled
* false if views are disabled, true if enabled
*/
public void addView(View view, boolean enabled) {
throw new RuntimeException("Not supported with MergeSpinnerAdapter");
}
/**
* Adds a list of views to the roster of things to appear
* in the aggregate list.
*
* @param views
* List of views to add
*/
public void addViews(List<View> views) {
throw new RuntimeException("Not supported with MergeSpinnerAdapter");
}
/**
* Adds a list of views to the roster of things to appear
* in the aggregate list.
*
* @param views
* List of views to add
* @param enabled
* false if views are disabled, true if enabled
*/
public void addViews(List<View> views, boolean enabled) {
throw new RuntimeException("Not supported with MergeSpinnerAdapter");
}
}
Loading…
Cancel
Save