diff --git a/api/src/com/todoroo/astrid/data/User.java b/api/src/com/todoroo/astrid/data/User.java index f5641badf..260f2cf34 100644 --- a/api/src/com/todoroo/astrid/data/User.java +++ b/api/src/com/todoroo/astrid/data/User.java @@ -58,6 +58,15 @@ public final class User extends RemoteModel { public static final LongProperty REMOTE_ID = new LongProperty( TABLE, REMOTE_ID_PROPERTY_NAME); + /** Friendship status. One of the STATUS constants below */ + public static final StringProperty STATUS = new StringProperty( + TABLE, "status"); + + /** Friendship tatus that needs to be reported to the server. + * One of the PENDING constants below */ + public static final StringProperty PENDING_STATUS = new StringProperty( + TABLE, "pendingStatus"); + /** List of all properties for this model */ public static final Property[] PROPERTIES = generateProperties(User.class); @@ -70,6 +79,8 @@ public final class User extends RemoteModel { defaultValues.put(NAME.name, ""); defaultValues.put(EMAIL.name, ""); defaultValues.put(PICTURE.name, ""); + defaultValues.put(STATUS.name, ""); + defaultValues.put(PENDING_STATUS.name, ""); } @Override @@ -77,6 +88,17 @@ public final class User extends RemoteModel { return defaultValues; } + public static final String STATUS_PENDING = "pending"; + public static final String STATUS_OTHER_PENDING = "other_pending"; + public static final String STATUS_FRIENDS = "friends"; + public static final String STATUS_IGNORED = "ignored"; + public static final String STATUS_BLOCKED = "blocked"; + + public static final String PENDING_REQUEST = "request"; + public static final String PENDING_APPROVE = "approve"; + public static final String PENDING_IGNORE = "ignore"; + public static final String PENDING_UNFRIEND = "unfriend"; + // --- data access boilerplate public User() { 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 da5090ec0..4890aadcd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java @@ -55,6 +55,7 @@ import com.todoroo.astrid.dao.TagDataDao; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.dao.UpdateDao; +import com.todoroo.astrid.dao.UserDao; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria; import com.todoroo.astrid.data.RemoteModel; @@ -99,6 +100,7 @@ public final class ActFmSyncService { @Autowired TaskDao taskDao; @Autowired TagDataDao tagDataDao; @Autowired UpdateDao updateDao; + @Autowired UserDao userDao; @Autowired MetadataDao metadataDao; @Autowired ABTestEventReportingService abTestEventReportingService; @@ -867,22 +869,31 @@ public final class ActFmSyncService { return result.optInt("time", 0); } + private void saveUsers(JSONArray users, HashSet ids) throws JSONException { + for (int i = 0; i < users.length(); i++) { + JSONObject userObject = users.getJSONObject(i); + ids.add(userObject.optLong("id")); + actFmDataService.saveUserData(userObject); } + + } + public int fetchUsers() throws JSONException, IOException { if (!checkForToken()) return 0; JSONObject result = actFmInvoker.invoke("user_list", "token", token); + JSONObject suggestedResult = actFmInvoker.invoke("suggested_user_list", + "token", token); JSONArray users = result.getJSONArray("list"); + JSONArray suggestedUsers = suggestedResult.getJSONArray("list"); + HashSet ids = new HashSet(); - if (users.length() > 0) + if (users.length() > 0 || suggestedUsers.length() > 0) Preferences.setBoolean(R.string.p_show_friends_view, true); - for (int i = 0; i < users.length(); i++) { - JSONObject userObject = users.getJSONObject(i); - ids.add(userObject.optLong("id")); - actFmDataService.saveUserData(userObject); - } + saveUsers(users, ids); + saveUsers(suggestedUsers, ids); Long[] idsArray = ids.toArray(new Long[ids.size()]); actFmDataService.userDao.deleteWhere(Criterion.not(User.REMOTE_ID.in(idsArray))); @@ -890,6 +901,34 @@ public final class ActFmSyncService { return result.optInt("time", 0); } + public void pushUser(User model) { + if (TextUtils.isEmpty(model.getValue(User.PENDING_STATUS))) + return; + if (model.getValue(User.REMOTE_ID) == 0) + return; + if (!checkForToken()) + return; + + try { + ArrayList params = new ArrayList(); + params.add("token"); params.add(token); + params.add("id"); params.add(model.getValue(User.REMOTE_ID)); + params.add("status"); params.add(model.getValue(User.PENDING_STATUS)); + + JSONObject result = actFmInvoker.invoke("user_set_status", params.toArray(new Object[params.size()])); + if (result.optString("status").equals("success")) { + String newStatus = result.optString("friendship_status"); + if (!TextUtils.isEmpty(newStatus)) { + model.setValue(User.STATUS, newStatus); + model.setValue(User.PENDING_STATUS, ""); + userDao.saveExisting(model); + } + } + } catch (IOException e) { + handleException("user-status", e); + } + } + /** * Fetch active tasks asynchronously @@ -1426,6 +1465,7 @@ public final class ActFmSyncService { model.setValue(User.NAME, json.optString("name")); model.setValue(User.EMAIL, json.optString("email")); model.setValue(User.PICTURE, json.optString("picture")); + model.setValue(User.STATUS, json.optString("status")); } public static void jsonFromUser(JSONObject json, User model) throws JSONException { 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 76902bb91..97404f99d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java @@ -29,6 +29,7 @@ import com.todoroo.astrid.billing.BillingConstants; import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; 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; @@ -60,6 +61,8 @@ public class ActFmSyncV2Provider extends SyncV2Provider { @Autowired MetadataService metadataService; + @Autowired UserDao userDao; + @Autowired Database database; private final PushQueuedArgs taskPusher = new PushQueuedArgs() { @@ -92,6 +95,19 @@ public class ActFmSyncV2Provider extends SyncV2Provider { } }; + private final PushQueuedArgs userPusher = new PushQueuedArgs() { + + @Override + public User getRemoteModelInstance(TodorooCursor cursor) { + return new User(cursor); + } + + @Override + public void pushRemoteModel(User model) { + actFmSyncService.pushUser(model); + } + }; + private final PushQueuedArgs filesPusher = new PushQueuedArgs() { @Override @@ -160,7 +176,7 @@ public class ActFmSyncV2Provider extends SyncV2Provider { actFmPreferenceService.recordSyncStart(); updateUserStatus(); - startUsersFetcher(callback, finisher); + startUsersSync(callback, finisher); startTagFetcher(callback, finisher); @@ -207,13 +223,14 @@ public class ActFmSyncV2Provider extends SyncV2Provider { } /** fetch changes to users/friends */ - private void startUsersFetcher(final SyncResultCallback callback, + private void startUsersSync(final SyncResultCallback callback, final AtomicInteger finisher) { new Thread(new Runnable() { @Override public void run() { int time = Preferences.getInt(LAST_USERS_FETCH_TIME, 0); try { + pushQueuedUsers(callback, finisher); time = actFmSyncService.fetchUsers(); Preferences.setInt(LAST_USERS_FETCH_TIME, time); } catch (JSONException e) { @@ -397,6 +414,17 @@ public class ActFmSyncV2Provider extends SyncV2Provider { } + private void pushQueuedUsers(final SyncResultCallback callback, + final AtomicInteger finisher) { + TodorooCursor users = userDao.query(Query.select(User.PROPERTIES).where( + Criterion.and(User.PENDING_STATUS.isNotNull(), Functions.length(User.PENDING_STATUS).gt(0)))); + try { + pushQueued(callback, finisher, users, true, userPusher); + } finally { + users.close(); + } + } + // --- synchronize list @Override diff --git a/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java b/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java index 5adb54cd1..aebe6df10 100644 --- a/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java +++ b/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java @@ -8,13 +8,11 @@ import com.todoroo.astrid.activity.FilterModeSpec; import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.FilterListItem; -import com.todoroo.astrid.api.FilterWithUpdate; -import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.ui.MainMenuPopover; public class PeopleFilterMode implements FilterModeSpec { - private AsyncImageView imageView; +// private AsyncImageView imageView; @Override public Filter getDefaultFilter(Context context) { @@ -29,17 +27,17 @@ public class PeopleFilterMode implements FilterModeSpec { @Override public void onFilterItemClickedCallback(FilterListItem item) { - if (imageView == null) - return; - if (item instanceof FilterWithUpdate) - imageView.setUrl(((FilterWithUpdate) item).imageUrl); - else - imageView.setUrl(null); - } - - public void setImageView(AsyncImageView imageView) { - this.imageView = imageView; +// if (imageView == null) +// return; +// if (item instanceof FilterWithUpdate) +// imageView.setUrl(((FilterWithUpdate) item).imageUrl); +// else +// imageView.setUrl(null); } +// +// public void setImageView(AsyncImageView imageView) { +// this.imageView = imageView; +// } @Override public int[] getForbiddenMenuItems() { diff --git a/astrid/plugin-src/com/todoroo/astrid/people/PersonViewFragment.java b/astrid/plugin-src/com/todoroo/astrid/people/PersonViewFragment.java index 80f303ed1..0f42808bf 100644 --- a/astrid/plugin-src/com/todoroo/astrid/people/PersonViewFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/people/PersonViewFragment.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.support.v4.view.Menu; import android.text.TextUtils; import android.view.View; +import android.view.ViewGroup; import android.widget.TextView; import com.timsu.astrid.R; @@ -17,10 +18,12 @@ import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; +import com.todoroo.astrid.actfm.sync.ActFmSyncService; import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.dao.UserDao; import com.todoroo.astrid.data.User; +import com.todoroo.astrid.helper.AsyncImageView; import com.todoroo.astrid.helper.ProgressBarSyncResultCallback; import com.todoroo.astrid.service.SyncV2Service; import com.todoroo.astrid.service.ThemeService; @@ -41,6 +44,12 @@ public class PersonViewFragment extends TaskListFragment { @Autowired ActFmPreferenceService actFmPreferenceService; + @Autowired ActFmSyncService actFmSyncService; + + private AsyncImageView userImage; + private TextView userSubtitle; + private TextView userStatusButton; + private User user; @Override @@ -50,6 +59,19 @@ public class PersonViewFragment extends TaskListFragment { user = userDao.fetch(extras.getLong(EXTRA_USER_ID_LOCAL), User.PROPERTIES); } ((TextView) getView().findViewById(android.R.id.empty)).setText(getEmptyDisplayString()); + + setupUserHeader(); + } + + private void setupUserHeader() { + if (user != null) { + userImage.setDefaultImageResource(R.drawable.icn_default_person_image); + userImage.setUrl(user.getValue(User.PICTURE)); + userSubtitle.setText(getUserSubtitleText()); + setupUserStatusButton(); + } else { + getView().findViewById(R.id.user_header).setVisibility(View.GONE); + } } @Override @@ -67,6 +89,65 @@ public class PersonViewFragment extends TaskListFragment { } + private String getUserSubtitleText() { + String status = user.getValue(User.STATUS); + String userName = user.getDisplayName(); + if (User.STATUS_PENDING.equals(status)) + return getString(R.string.actfm_friendship_pending, userName); + else if (User.STATUS_BLOCKED.equals(status)) + return getString(R.string.actfm_friendship_blocked, userName); + else if (User.STATUS_FRIENDS.equals(status)) + return getString(R.string.actfm_friendship_friends, userName); + else if (User.STATUS_OTHER_PENDING.equals(status)) + return getString(R.string.actfm_friendship_other_pending, userName); + else return getString(R.string.actfm_friendship_no_status, userName); + + } + + private void setupUserStatusButton() { + String status = user.getValue(User.STATUS); + userStatusButton.setVisibility(View.VISIBLE); + if (TextUtils.isEmpty(status)) + userStatusButton.setText(getString(R.string.actfm_friendship_connect)); + else if (User.STATUS_OTHER_PENDING.equals(status)) + userStatusButton.setText(getString(R.string.actfm_friendship_accept)); + else + userStatusButton.setVisibility(View.GONE); + } + + @Override + protected void setUpUiComponents() { + super.setUpUiComponents(); + userImage = (AsyncImageView) getView().findViewById(R.id.user_image); + userSubtitle = (TextView) getView().findViewById(R.id.user_subtitle); + userStatusButton = (TextView) getActivity().findViewById(R.id.person_image); + } + + @Override + protected View getListBody(ViewGroup root) { + ViewGroup parent = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.task_list_body_user, root, false); + + View taskListView = super.getListBody(parent); + parent.addView(taskListView); + + return parent; + } + + public void handleStatusButtonClicked() { + String status = user.getValue(User.STATUS); + if (TextUtils.isEmpty(status)) { // Add friend case + user.setValue(User.PENDING_STATUS, User.PENDING_REQUEST); + } else if (User.STATUS_OTHER_PENDING.equals(status)) { // Accept friend case + user.setValue(User.PENDING_STATUS, User.PENDING_APPROVE); + } + + if (user.getSetValues().containsKey(User.PENDING_STATUS.name)) { + userDao.saveExisting(user); + userStatusButton.setVisibility(View.GONE); + refreshData(false); + } + } + @Override protected void addSyncRefreshMenuItem(Menu menu, int themeFlags) { if(actFmPreferenceService.isLoggedIn()) { @@ -98,21 +179,35 @@ public class PersonViewFragment extends TaskListFragment { } } + @Override + protected void refresh() { + super.refresh(); + setupUserHeader(); + } + private void refreshData(final boolean manual) { if (user != null) { ((TextView) getView().findViewById(android.R.id.empty)).setText(R.string.DLG_loading); - - syncService.synchronizeList(user, manual, new ProgressBarSyncResultCallback(getActivity(), this, - R.id.progressBar, new Runnable() { + new Thread() { @Override public void run() { - if (manual) - ContextManager.getContext().sendBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH)); - else - refresh(); - ((TextView) getView().findViewById(android.R.id.empty)).setText(getEmptyDisplayString()); + if (!TextUtils.isEmpty(user.getValue(User.PENDING_STATUS))) { + actFmSyncService.pushUser(user); + user = userDao.fetch(user.getId(), User.PROPERTIES); + } + syncService.synchronizeList(user, manual, new ProgressBarSyncResultCallback(getActivity(), PersonViewFragment.this, + R.id.progressBar, new Runnable() { + @Override + public void run() { + if (manual) + ContextManager.getContext().sendBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH)); + else + refresh(); + ((TextView) getView().findViewById(android.R.id.empty)).setText(getEmptyDisplayString()); + } + })); } - })); + }.start(); } } diff --git a/astrid/res/layout/header_nav_views.xml b/astrid/res/layout/header_nav_views.xml index 8430fb867..c66bd04a3 100644 --- a/astrid/res/layout/header_nav_views.xml +++ b/astrid/res/layout/header_nav_views.xml @@ -54,11 +54,15 @@ -