diff --git a/astrid/.classpath b/astrid/.classpath
index cc10d291f..a20a47fca 100644
--- a/astrid/.classpath
+++ b/astrid/.classpath
@@ -30,5 +30,6 @@
+
diff --git a/astrid/libs/CWAC-SackOfViewsAdapter.jar b/astrid/libs/CWAC-SackOfViewsAdapter.jar
new file mode 100644
index 000000000..0b43a064f
Binary files /dev/null and b/astrid/libs/CWAC-SackOfViewsAdapter.jar differ
diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java b/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java
index 43b88da6b..5d081320c 100644
--- a/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java
+++ b/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java
@@ -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 listValues = new ArrayList();
-
private final int loginRequestCode;
private boolean assignedToMe = false;
@@ -351,16 +350,17 @@ public class EditPeopleControlSet extends PopupControlSet {
HashSet emails = new HashSet();
HashMap names = new HashMap();
+ ArrayList coreUsers = new ArrayList();
+ ArrayList listUsers = new ArrayList();
+ ArrayList astridUsers = new ArrayList();
+
int assignedIndex = 0;
try {
- if(t.getValue(Task.USER_ID) > 0) {
- JSONObject user = new JSONObject(t.getValue(Task.USER));
- sharedPeople.add(0, user);
- }
-
+ ArrayList coreUsersJson = new ArrayList();
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)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 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 sharedPeople) {
+ @SuppressWarnings("nls")
+ private ArrayList convertJsonUsersToAssignedUsers(ArrayList jsonUsers,
+ HashSet userIds, HashSet emails, HashMap names) {
+ ArrayList users = new ArrayList();
+ 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... 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 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 getAstridFriends() {
+ ArrayList astridFriends = new ArrayList();
TodorooCursor 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 {
- public AssignedUserAdapter(Context context, ArrayList people) {
+ private final int positionOffset;
+
+ public AssignedUserAdapter(Context context, ArrayList 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"));
}
diff --git a/astrid/res/layout/list_header.xml b/astrid/res/layout/list_header.xml
new file mode 100644
index 000000000..10f6ab8ca
--- /dev/null
+++ b/astrid/res/layout/list_header.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/astrid/res/values/strings-actfm.xml b/astrid/res/values/strings-actfm.xml
index c2256c781..0015d6ecd 100644
--- a/astrid/res/values/strings-actfm.xml
+++ b/astrid/res/values/strings-actfm.xml
@@ -169,6 +169,12 @@
Help me get this done!
+
+ List Members
+
+
+ Astrid Friends
+
Create a shared tag?
@@ -199,6 +205,7 @@
Log in
Make private
+
diff --git a/astrid/res/values/styles.xml b/astrid/res/values/styles.xml
index 5c99490fa..1881b52da 100644
--- a/astrid/res/values/styles.xml
+++ b/astrid/res/values/styles.xml
@@ -329,6 +329,13 @@
- bold
- ?attr/asTextColor
+
+
diff --git a/astrid/src/com/commonsware/cwac/merge/MergeAdapter.java b/astrid/src/com/commonsware/cwac/merge/MergeAdapter.java
new file mode 100644
index 000000000..846299d17
--- /dev/null
+++ b/astrid/src/com/commonsware/cwac/merge/MergeAdapter.java
@@ -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 pieces=new ArrayList();
+
+ /**
+ * 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 list=new ArrayList(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 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 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 sections=new ArrayList