Merge pull request #173 from sbosley/120405_sb_pick_contact

Contacts picker!
pull/14/head
Tim Su 12 years ago
commit 076eeee13c

@ -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

@ -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>

@ -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<Metadata> nonSharedTags = new ArrayList<Metadata>();
@ -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<AssignedToUser> listValues = new ArrayList<AssignedToUser>();
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<AssignedChangedListener> listeners = new LinkedList<AssignedChangedListener>();
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<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(TaskService.TRANS_TAGS) != null &&
((HashSet<String>)t.getTransitory(TaskService.TRANS_TAGS)).size() > 0;
if (actFmPreferenceService.isLoggedIn() && hasTags) {
boolean hasTags = t.getTransitory("tags") != null &&
((HashSet<String>)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<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));
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<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();
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<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")
@ -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();

@ -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<User> 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();
}
}
}

@ -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<Long> ids = new HashSet<Long>();
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);

@ -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) {

@ -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);

@ -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

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/btn_add_pressed" />
<item android:drawable="@drawable/btn_add_normal" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -2,12 +2,12 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:astrid="http://schemas.android.com/apk/res/com.timsu.astrid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dip">
<com.todoroo.astrid.helper.AsyncImageView android:id="@+id/person_image"
android:layout_width="40dip"
android:layout_height="44dip"
android:layout_width="35dip"
android:layout_height="35dip"
android:gravity="center"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter"
@ -15,8 +15,8 @@
<CheckedTextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_height="wrap_content"
style="@style/TextAppearance.TEA_assigned"
android:textColor="?attr/asTextColor"
android:gravity="center_vertical"
android:checkMark="?android:attr/listChoiceIndicatorSingle"

@ -23,8 +23,16 @@
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center"
android:layout_marginLeft="2dip"
android:gravity="center_vertical" />
<Button
android:id="@+id/edit_dlg_ok"
android:background="@drawable/btn_add"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center"
android:layout_marginLeft="2dip"
android:layout_marginRight="3dip"
android:layout_marginLeft="4dip"
android:gravity="center_vertical" />
</LinearLayout>
@ -33,6 +41,5 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="100"/>
<include layout="@layout/control_dialog_ok"/>
</LinearLayout>

@ -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" />

@ -139,6 +139,9 @@
<!-- task sharing dialog: anyone -->
<string name="actfm_EPA_unassigned">Unassigned</string>
<!-- task sharing dialog: choose a contact -->
<string name="actfm_EPA_choose_contact">Choose a contact</string>
<!-- task sharing dialog: use task rabbit -->
<string name="actfm_EPA_task_rabbit">Outsource it!</string>
@ -166,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>
@ -196,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 == -->

@ -450,6 +450,8 @@
<string name="WSV_not_online">I can do more when connected to the Internet. Please check your connection.</string>
<string name="TEA_contact_error">Sorry! We couldn\'t find an email address for the selected contact.</string>
<!-- ============================================= IntroductionActivity == -->
<!-- Introduction Window title -->

@ -288,6 +288,10 @@
<style name="TextAppearance.GEN_EditLabel.DLG_EditLabel">
</style>
<style name="TextAppearance.TEA_assigned">
<item name="android:textSize">18sp</item>
</style>
<style name="Theme.WhenDialog">
<item name="android:windowAnimationStyle">@style/TEA_WhenDialog_Anim</item>
@ -325,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");
}
}

@ -149,6 +149,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
public static final int REQUEST_LOG_IN = 0;
private static final int REQUEST_VOICE_RECOG = 10;
public static final int REQUEST_CODE_CONTACT = 20;
// --- menu codes
@ -1058,11 +1059,9 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (taskRabbitControl != null && taskRabbitControl.activityResult(requestCode, resultCode, data)) {
return;
}
else if (editNotes != null && editNotes.activityResult(requestCode, resultCode, data)) {
} else if (editNotes != null && editNotes.activityResult(requestCode, resultCode, data)) {
return;
}
else if (requestCode == REQUEST_VOICE_RECOG
} else if (requestCode == REQUEST_VOICE_RECOG
&& resultCode == Activity.RESULT_OK) {
// handle the result of voice recognition, put it into the
// appropiate textfield

@ -18,6 +18,7 @@ import com.todoroo.astrid.data.StoreObject;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.Update;
import com.todoroo.astrid.data.User;
import com.todoroo.astrid.provider.Astrid2TaskProvider;
import com.todoroo.astrid.provider.Astrid3ContentProvider;
import com.todoroo.astrid.widget.TasksWidget;
@ -37,7 +38,7 @@ public class Database extends AbstractDatabase {
* Database version number. This variable must be updated when database
* tables are updated, as it determines whether a database needs updating.
*/
public static final int VERSION = 22;
public static final int VERSION = 23;
/**
* Database name (must be unique)
@ -54,6 +55,7 @@ public class Database extends AbstractDatabase {
StoreObject.TABLE,
TagData.TABLE,
Update.TABLE,
User.TABLE
};
// --- listeners
@ -289,7 +291,6 @@ public class Database extends AbstractDatabase {
database.execSQL(changeZeroes);
onCreateTables();
} catch (SQLiteException e) {
Log.e("astrid", "db-upgrade-" + oldVersion + "-" + newVersion, e);
}
@ -302,6 +303,12 @@ public class Database extends AbstractDatabase {
catch (SQLiteException e) {
Log.e("astrid", "db-upgrade-" + oldVersion + "-" + newVersion, e);
}
case 22: try {
database.execSQL(createTableSql(visitor, User.TABLE.name, User.PROPERTIES));
onCreateTables();
} catch (SQLiteException e) {
Log.e("astrid", "db-upgrade-" + oldVersion + "-" + newVersion, e);
}
return true;
}

@ -0,0 +1,17 @@
package com.todoroo.astrid.dao;
import com.todoroo.andlib.data.DatabaseDao;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.astrid.data.User;
public class UserDao extends DatabaseDao<User> {
@Autowired Database database;
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UR_UNINIT_READ")
public UserDao() {
super(User.class);
DependencyInjectionService.getInstance().inject(this);
setDatabase(database);
}
}

@ -18,6 +18,7 @@ import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.UpdateDao;
import com.todoroo.astrid.dao.UserDao;
import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.GtasksMetadataService;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
@ -66,6 +67,7 @@ public class AstridDependencyInjector extends AbstractDependencyInjector {
injectables.put("tagDataDao", TagDataDao.class);
injectables.put("storeObjectDao", StoreObjectDao.class);
injectables.put("updateDao", UpdateDao.class);
injectables.put("userDao", UserDao.class);
// com.todoroo.astrid.service
injectables.put("taskService", TaskService.class);

@ -81,10 +81,10 @@ public class EditNotesControlSet extends PopupControlSet {
}
@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

@ -27,10 +27,14 @@ public abstract class PopupControlSet extends TaskEditControlSet {
protected final TextView displayText;
private final String titleString;
final DialogInterface.OnClickListener okListener = new DialogInterface.OnClickListener() {
public interface PopupDialogClickListener {
public boolean onClick(DialogInterface d, int which);
}
final PopupDialogClickListener okListener = new PopupDialogClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
onOkClick();
public boolean onClick(DialogInterface d, int which) {
return onOkClick();
}
};
@ -66,7 +70,7 @@ public abstract class PopupControlSet extends TaskEditControlSet {
return displayView;
}
protected Dialog buildDialog(String title, final DialogInterface.OnClickListener okClickListener, DialogInterface.OnCancelListener cancelClickListener) {
protected Dialog buildDialog(String title, final PopupDialogClickListener okClickListener, DialogInterface.OnCancelListener cancelClickListener) {
int theme = ThemeService.getEditDialogTheme();
dialog = new Dialog(activity, theme);
if (title.length() == 0)
@ -82,8 +86,8 @@ public abstract class PopupControlSet extends TaskEditControlSet {
dismiss.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
okClickListener.onClick(dialog, 0);
DialogUtilities.dismissDialog(activity, dialog);
if (okClickListener.onClick(dialog, 0))
DialogUtilities.dismissDialog(activity, dialog);
}
});
}
@ -122,8 +126,13 @@ public abstract class PopupControlSet extends TaskEditControlSet {
// Subclasses can override
}
protected void onOkClick() {
/**
* @return true if the dialog should be dismissed as the result of
* the click. Default is true.
*/
protected boolean onOkClick() {
refreshDisplayView();
return true;
}
protected void onCancelClick() {

Loading…
Cancel
Save