diff --git a/api/src/com/todoroo/astrid/data/User.java b/api/src/com/todoroo/astrid/data/User.java
index 308f94e38..0d6d6e2cc 100644
--- a/api/src/com/todoroo/astrid/data/User.java
+++ b/api/src/com/todoroo/astrid/data/User.java
@@ -53,18 +53,6 @@ public final class User extends RemoteModel {
public static final StringProperty PICTURE = new StringProperty(
TABLE, "picture");
- /** User picture thumbnail */
- public static final StringProperty THUMB = new StringProperty(
- TABLE, "thumb");
-
- /** User last activity string */
- public static final StringProperty LAST_ACTIVITY = new StringProperty(
- TABLE, "lastActivity");
-
- /** User last activity date */
- public static final LongProperty LAST_ACTIVITY_DATE = new LongProperty(
- TABLE, "lastActivityDate");
-
/** Remote id */
public static final LongProperty REMOTE_ID = new LongProperty(
TABLE, REMOTE_ID_PROPERTY_NAME);
@@ -81,9 +69,6 @@ public final class User extends RemoteModel {
defaultValues.put(NAME.name, "");
defaultValues.put(EMAIL.name, "");
defaultValues.put(PICTURE.name, "");
- defaultValues.put(THUMB.name, "");
- defaultValues.put(LAST_ACTIVITY.name, "");
- defaultValues.put(LAST_ACTIVITY_DATE.name, 0);
}
@Override
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 8db6b7c99..0eb7153ca 100644
--- a/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java
+++ b/astrid/plugin-src/com/todoroo/astrid/actfm/EditPeopleControlSet.java
@@ -15,7 +15,10 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.database.Cursor;
import android.graphics.Color;
+import android.net.Uri;
+import android.provider.ContactsContract;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -37,17 +40,23 @@ 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;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
+import com.todoroo.andlib.sql.Order;
+import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
+import com.todoroo.astrid.activity.TaskEditFragment;
+import com.todoroo.astrid.dao.UserDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
+import com.todoroo.astrid.data.User;
import com.todoroo.astrid.helper.AsyncImageView;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.MetadataService;
@@ -64,6 +73,8 @@ public class EditPeopleControlSet extends PopupControlSet {
public static final String EXTRA_TASK_ID = "task"; //$NON-NLS-1$
+ private static final String CONTACT_CHOOSER_USER = "the_contact_user"; //$NON-NLS-1$
+
private Task task;
private final ArrayList nonSharedTags = new ArrayList();
@@ -74,6 +85,8 @@ public class EditPeopleControlSet extends PopupControlSet {
@Autowired TaskService taskService;
+ @Autowired UserDao userDao;
+
@Autowired MetadataService metadataService;
@Autowired ExceptionService exceptionService;
@@ -102,16 +115,18 @@ public class EditPeopleControlSet extends PopupControlSet {
private final View assignedClear;
- private final ArrayList listValues = new ArrayList();
-
private final int loginRequestCode;
private boolean assignedToMe = false;
private AssignedToUser taskRabbitUser = null;
+ private AssignedToUser contactPickerUser = null;
+
private boolean loadedUI = false;
+ private boolean dontClearAssignedCustom = false;
+
private final List listeners = new LinkedList();
public interface AssignedChangedListener {
@@ -175,7 +190,9 @@ public class EditPeopleControlSet extends PopupControlSet {
@Override
public void readFromTask(Task sourceTask) {
setTask(sourceTask);
- assignedCustom.setText(""); //$NON-NLS-1$
+ if (!dontClearAssignedCustom)
+ assignedCustom.setText(""); //$NON-NLS-1$
+ dontClearAssignedCustom = false;
setUpData(task, null);
}
@@ -333,112 +350,200 @@ 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(TaskService.TRANS_TAGS) != null &&
- ((HashSet)t.getTransitory(TaskService.TRANS_TAGS)).size() > 0;
- if (actFmPreferenceService.isLoggedIn() && hasTags) {
+ boolean hasTags = t.getTransitory("tags") != null &&
+ ((HashSet)t.getTransitory("tags")).size() > 0;
+ boolean addUnassigned = actFmPreferenceService.isLoggedIn() && hasTags;
+ 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);
}
- // 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));
+ int contactsIndex = addUnassigned ? 2 : 1;
+ 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));
- listValues.add(taskRabbitUser);
+ int taskRabbitIndex = addUnassigned ? 3 : 2;
+ coreUsers.add(taskRabbitIndex, taskRabbitUser);
if(l.didPostToTaskRabbit()){
- assignedIndex = listValues.size()-1;
+ assignedIndex = taskRabbitIndex;
}
}
}
+
+ 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();
}
});
}
+ @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();
+ for (users.moveToFirst(); !users.isAfterLast(); users.moveToNext()) {
+ user.readFromCursor(users);
+ JSONObject userJson = new JSONObject();
+ try {
+ ActFmSyncService.JsonHelper.jsonFromUser(userJson, user);
+ astridFriends.add(userJson);
+ } catch (JSONException e) {
+ // Ignored
+ }
+ }
+ } 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")
@@ -448,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"));
}
@@ -487,8 +585,6 @@ public class EditPeopleControlSet extends PopupControlSet {
for (AssignedChangedListener l : listeners) {
if(l.showTaskRabbitForUser(user.label, user.user)) {
-// assignedList.setItemChecked(selected, true);
-// assignedList.setItemChecked(position, false);
assignedDisplay.setText(user.toString());
assignedCustom.setText(""); //$NON-NLS-1$
DialogUtilities.dismissDialog(activity, dialog);
@@ -496,6 +592,13 @@ public class EditPeopleControlSet extends PopupControlSet {
}
}
+
+ if (user.user.has(CONTACT_CHOOSER_USER)) {
+ Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
+ fragment.startActivityForResult(intent, TaskEditFragment.REQUEST_CODE_CONTACT);
+ return;
+ }
+
assignedDisplay.setText(user.toString());
assignedCustom.setText(""); //$NON-NLS-1$
selected = position;
@@ -584,10 +687,7 @@ public class EditPeopleControlSet extends PopupControlSet {
return true;
AssignedToUser item = (AssignedToUser) assignedList.getAdapter().getItem(assignedList.getCheckedItemPosition());
if (item != null) {
- if (item.equals(taskRabbitUser)) { //don't want to ever set the user as the task rabbit user
-
- /*item = (AssignedToUser) assignedList.getAdapter().getItem(0);
- selected = 0;*/
+ if (item.equals(taskRabbitUser) || item.equals(contactPickerUser)) { //don't want to ever set the user as the task rabbit user
return true;
}
userJson = item.user;
@@ -811,9 +911,42 @@ public class EditPeopleControlSet extends PopupControlSet {
// clear user values & reset them to trigger a save
task.clearValue(Task.USER_ID);
task.clearValue(Task.USER);
- }
- else if (requestCode == loginRequestCode)
+ } else if (requestCode == loginRequestCode) {
makePrivateTask();
+ } else if (requestCode == TaskEditFragment.REQUEST_CODE_CONTACT && resultCode == Activity.RESULT_OK) {
+ Uri contactData = data.getData();
+ String contactId = contactData.getLastPathSegment();
+ String[] args = { contactId };
+ String[] projection = { ContactsContract.CommonDataKinds.Email.DATA };
+ String selection = ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?"; //$NON-NLS-1$
+ Cursor emailCursor = activity.managedQuery(ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, args, null);
+ if (emailCursor.getCount() > 0) {
+ emailCursor.moveToFirst();
+ int emailIndex = emailCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
+ String email = emailCursor.getString(emailIndex);
+ if (!TextUtils.isEmpty(email)) {
+ String[] nameProjection = { ContactsContract.Contacts.DISPLAY_NAME };
+ Cursor nameCursor = activity.managedQuery(contactData, nameProjection, null, null, null);
+ if (nameCursor.getCount() > 0) {
+ nameCursor.moveToFirst();
+ int nameIndex = nameCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
+ String name = nameCursor.getString(nameIndex);
+ if (!TextUtils.isEmpty(name)) {
+ StringBuilder fullName = new StringBuilder();
+ fullName.append(name).append(" <").append(email).append('>'); //$NON-NLS-1$
+ email = fullName.toString();
+ }
+ }
+ assignedCustom.setText(email);
+ dontClearAssignedCustom = true;
+ dialog.dismiss();
+ } else {
+ DialogUtilities.okDialog(activity, activity.getString(R.string.TEA_contact_error), null);
+ }
+ } else {
+ DialogUtilities.okDialog(activity, activity.getString(R.string.TEA_contact_error), null);
+ }
+ }
}
@Override
@@ -828,6 +961,21 @@ public class EditPeopleControlSet extends PopupControlSet {
}
}
+ @Override
+ protected boolean onOkClick() {
+ if (!TextUtils.isEmpty(assignedCustom.getText())) {
+ JSONObject assigned = PeopleContainer.createUserJson(assignedCustom);
+ String email = assigned.optString("email"); //$NON-NLS-1$
+ if (!TextUtils.isEmpty(email) && email.indexOf('@') == -1) {
+ assignedCustom.requestFocus();
+ DialogUtilities.okDialog(activity, activity.getString(R.string.actfm_EPA_invalid_email,
+ assigned.optString("email")), null); //$NON-NLS-1$
+ return false;
+ }
+ }
+ return super.onOkClick();
+ }
+
@Override
protected void additionalDialogSetup() {
super.additionalDialogSetup();
diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java
index 49b9168b8..1621a5ac1 100644
--- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java
+++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java
@@ -22,9 +22,11 @@ import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
+import com.todoroo.astrid.dao.UserDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
+import com.todoroo.astrid.data.User;
import com.todoroo.astrid.notes.NoteMetadata;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TagDataService;
@@ -46,6 +48,8 @@ public final class ActFmDataService {
@Autowired TaskDao taskDao;
+ @Autowired UserDao userDao;
+
@Autowired MetadataService metadataService;
@Autowired ActFmPreferenceService actFmPreferenceService;
@@ -198,4 +202,26 @@ public final class ActFmDataService {
}
}
+ /**
+ * Save / Merge JSON user
+ * @param userObject
+ * @throws JSONException
+ */
+ @SuppressWarnings("nls")
+ public void saveUserData(JSONObject userObject) throws JSONException {
+ TodorooCursor cursor = userDao.query(Query.select(User.PROPERTIES).where(
+ User.REMOTE_ID.eq(userObject.get("id"))));
+ try {
+ cursor.moveToFirst();
+ User user = new User();
+ if (!cursor.isAfterLast()) {
+ user.readFromCursor(cursor);
+ }
+ ActFmSyncService.JsonHelper.userFromJson(userObject, user);
+ userDao.persist(user);
+
+ } finally {
+ cursor.close();
+ }
+ }
}
diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java
index 74960799a..e218524ac 100644
--- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java
+++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java
@@ -59,6 +59,7 @@ import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskApiDao;
import com.todoroo.astrid.data.Update;
+import com.todoroo.astrid.data.User;
import com.todoroo.astrid.helper.ImageDiskCache;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.StatisticsConstants;
@@ -727,6 +728,29 @@ public final class ActFmSyncService {
return result.optInt("time", 0);
}
+ public int fetchUsers(int serverTime) throws JSONException, IOException {
+ if (!checkForToken())
+ return 0;
+
+ JSONObject result = actFmInvoker.invoke("user_list",
+ "token", token, "modified_after", serverTime);
+ JSONArray users = result.getJSONArray("list");
+ HashSet ids = new HashSet();
+ for (int i = 0; i < users.length(); i++) {
+ JSONObject userObject = users.getJSONObject(i);
+ ids.add(userObject.optLong("id"));
+ actFmDataService.saveUserData(userObject);
+ }
+
+ if (serverTime == 0) {
+ Long[] allIds = ids.toArray(new Long[ids.size()]);
+ actFmDataService.userDao.deleteWhere(Criterion.not(User.REMOTE_ID.in(allIds)));
+ }
+
+ return result.optInt("time", 0);
+ }
+
+
/**
* Fetch active tasks asynchronously
* @param manual
@@ -1134,6 +1158,20 @@ public final class ActFmSyncService {
return item.optLong(key, 0) * 1000L;
}
+ public static void userFromJson(JSONObject json, User model) throws JSONException {
+ model.setValue(User.REMOTE_ID, json.getLong("id"));
+ model.setValue(User.NAME, json.optString("name"));
+ model.setValue(User.EMAIL, json.optString("email"));
+ model.setValue(User.PICTURE, json.optString("picture"));
+ }
+
+ public static void jsonFromUser(JSONObject json, User model) throws JSONException {
+ json.put("id", model.getValue(User.REMOTE_ID));
+ json.put("name", model.getValue(User.NAME));
+ json.put("email", model.getValue(User.EMAIL));
+ json.put("picture", model.getValue(User.PICTURE));
+ }
+
public static void updateFromJson(JSONObject json, Update model) throws JSONException {
model.setValue(Update.REMOTE_ID, json.getLong("id"));
readUser(json.getJSONObject("user"), model, Update.USER_ID, Update.USER);
diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java
index 3912c4107..b295c842f 100644
--- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java
+++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java
@@ -103,6 +103,8 @@ public class ActFmSyncV2Provider extends SyncV2Provider {
private static final String LAST_TAG_FETCH_TIME = "actfm_lastTag"; //$NON-NLS-1$
+ private static final String LAST_USERS_FETCH_TIME = "actfm_lastUsers"; //$NON-NLS-1$
+
// --- synchronize active tasks
@Override
@@ -112,12 +114,15 @@ public class ActFmSyncV2Provider extends SyncV2Provider {
new Thread(new Runnable() {
public void run() {
callback.started();
- callback.incrementMax(100);
+ callback.incrementMax(120);
- final AtomicInteger finisher = new AtomicInteger(2);
+ final AtomicInteger finisher = new AtomicInteger(3);
actFmPreferenceService.recordSyncStart();
+
+ startUsersFetcher(manual, callback, finisher);
+
startTagFetcher(callback, finisher);
actFmSyncService.waitUntilEmpty();
@@ -128,6 +133,31 @@ public class ActFmSyncV2Provider extends SyncV2Provider {
}).start();
}
+ /** fetch changes to users/friends */
+ private void startUsersFetcher(final boolean manual, final SyncResultCallback callback,
+ final AtomicInteger finisher) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int time = manual ? 0 : Preferences.getInt(LAST_USERS_FETCH_TIME, 0);
+ try {
+ time = actFmSyncService.fetchUsers(time);
+ Preferences.setInt(LAST_USERS_FETCH_TIME, time);
+ } catch (JSONException e) {
+ handler.handleException("actfm-sync", e, e.toString()); //$NON-NLS-1$
+ } catch (IOException e) {
+ handler.handleException("actfm-sync", e, e.toString()); //$NON-NLS-1$
+ } finally {
+ callback.incrementProgress(20);
+ if(finisher.decrementAndGet() == 0) {
+ actFmPreferenceService.recordSuccessfulSync();
+ callback.finished();
+ }
+ }
+ }
+ }).start();
+ }
+
/** fetch changes to tags */
private void startTagFetcher(final SyncResultCallback callback,
final AtomicInteger finisher) {
diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java
index c1ffacc9c..4b714bdfd 100644
--- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java
+++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java
@@ -389,17 +389,17 @@ public class RepeatControlSet extends PopupControlSet {
}
@Override
- protected Dialog buildDialog(String title, final DialogInterface.OnClickListener okListener, final DialogInterface.OnCancelListener cancelListener) {
+ protected Dialog buildDialog(String title, final PopupDialogClickListener okListener, final DialogInterface.OnCancelListener cancelListener) {
- DialogInterface.OnClickListener doRepeatButton = new DialogInterface.OnClickListener() {
+ PopupDialogClickListener doRepeatButton = new PopupDialogClickListener() {
@Override
- public void onClick(DialogInterface d, int which) {
+ public boolean onClick(DialogInterface d, int which) {
doRepeat = true;
- okListener.onClick(d, which);
for (RepeatChangedListener l : listeners) {
l.repeatChanged(doRepeat);
}
+ return okListener.onClick(d, which);
}
};
final Dialog d = super.buildDialog(title, doRepeatButton, cancelListener);
diff --git a/astrid/plugin-src/com/todoroo/astrid/taskrabbit/TaskRabbitNameControlSet.java b/astrid/plugin-src/com/todoroo/astrid/taskrabbit/TaskRabbitNameControlSet.java
index d1720247d..37ecedf47 100644
--- a/astrid/plugin-src/com/todoroo/astrid/taskrabbit/TaskRabbitNameControlSet.java
+++ b/astrid/plugin-src/com/todoroo/astrid/taskrabbit/TaskRabbitNameControlSet.java
@@ -176,10 +176,10 @@ public class TaskRabbitNameControlSet extends PopupControlSet implements TaskRab
}
@Override
- protected void onOkClick() {
- super.onOkClick();
+ protected boolean onOkClick() {
InputMethodManager imm = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
+ return super.onOkClick();
}
@Override
diff --git a/astrid/res/drawable/btn_add.xml b/astrid/res/drawable/btn_add.xml
new file mode 100644
index 000000000..eb597adab
--- /dev/null
+++ b/astrid/res/drawable/btn_add.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/astrid/res/drawable/btn_add_normal.png b/astrid/res/drawable/btn_add_normal.png
new file mode 100644
index 000000000..56cf1c7b2
Binary files /dev/null and b/astrid/res/drawable/btn_add_normal.png differ
diff --git a/astrid/res/drawable/btn_add_pressed.png b/astrid/res/drawable/btn_add_pressed.png
new file mode 100644
index 000000000..25e764d8f
Binary files /dev/null and b/astrid/res/drawable/btn_add_pressed.png differ
diff --git a/astrid/res/layout/assigned_adapter_row.xml b/astrid/res/layout/assigned_adapter_row.xml
index 95dbffd16..a8644ccda 100644
--- a/astrid/res/layout/assigned_adapter_row.xml
+++ b/astrid/res/layout/assigned_adapter_row.xml
@@ -2,12 +2,12 @@
+
@@ -33,6 +41,5 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="100"/>
-
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 0f09c32a1..0015d6ecd 100644
--- a/astrid/res/values/strings-actfm.xml
+++ b/astrid/res/values/strings-actfm.xml
@@ -139,6 +139,9 @@
Unassigned
+
+ Choose a contact
+
Outsource it!
@@ -166,6 +169,12 @@
Help me get this done!
+
+ List Members
+
+
+ Astrid Friends
+
Create a shared tag?
@@ -196,6 +205,7 @@
Log in
Make private
+
diff --git a/astrid/res/values/strings-core.xml b/astrid/res/values/strings-core.xml
index 15b4074b9..4142dc5a3 100644
--- a/astrid/res/values/strings-core.xml
+++ b/astrid/res/values/strings-core.xml
@@ -450,6 +450,8 @@
I can do more when connected to the Internet. Please check your connection.
+ Sorry! We couldn\'t find an email address for the selected contact.
+
diff --git a/astrid/res/values/styles.xml b/astrid/res/values/styles.xml
index 2cdf5d88f..1881b52da 100644
--- a/astrid/res/values/styles.xml
+++ b/astrid/res/values/styles.xml
@@ -288,6 +288,10 @@
+
+
+
+
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