/** * Copyright 2010 Mark Wyszomierski */ package com.joelapenna.foursquared; import com.joelapenna.foursquare.Foursquare; import com.joelapenna.foursquare.error.FoursquareException; import com.joelapenna.foursquare.types.Group; import com.joelapenna.foursquare.types.Tip; import com.joelapenna.foursquare.types.Todo; import com.joelapenna.foursquared.app.LoadableListActivityWithViewAndHeader; import com.joelapenna.foursquared.location.LocationUtils; import com.joelapenna.foursquared.util.MenuUtils; import com.joelapenna.foursquared.util.NotificationsUtil; import com.joelapenna.foursquared.util.TipUtils; import com.joelapenna.foursquared.widget.SegmentedButton; import com.joelapenna.foursquared.widget.SegmentedButton.OnClickListenerSegmentedButton; import com.joelapenna.foursquared.widget.TodosListAdapter; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Location; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.LinearLayout; import android.widget.ListView; import java.util.Observable; import java.util.Observer; /** * Shows a list of a user's todos. We can operate on the logged-in user, * or a friend user, specified through the intent extras. * * If operating on the logged-in user, we remove items from the todo list * if they mark a todo as done or un-mark it. If operating on another user, * we do not remove them from the list. * * @date September 12, 2010 * @author Mark Wyszomierski (markww@gmail.com) */ public class TodosActivity extends LoadableListActivityWithViewAndHeader { static final String TAG = "TodosActivity"; static final boolean DEBUG = FoursquaredSettings.DEBUG; public static final String INTENT_EXTRA_USER_ID = Foursquared.PACKAGE_NAME + ".TodosActivity.INTENT_EXTRA_USER_ID"; public static final String INTENT_EXTRA_USER_NAME = Foursquared.PACKAGE_NAME + ".TodosActivity.INTENT_EXTRA_USER_NAME"; private static final int ACTIVITY_TIP = 500; private StateHolder mStateHolder; private TodosListAdapter mListAdapter; private SearchLocationObserver mSearchLocationObserver = new SearchLocationObserver(); private View mLayoutEmpty; private static final int MENU_REFRESH = 0; private BroadcastReceiver mLoggedOutReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "onReceive: " + intent); finish(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); registerReceiver(mLoggedOutReceiver, new IntentFilter(Foursquared.INTENT_ACTION_LOGGED_OUT)); Object retained = getLastNonConfigurationInstance(); if (retained != null && retained instanceof StateHolder) { mStateHolder = (StateHolder) retained; mStateHolder.setActivity(this); } else { // Optional user id and username, if not present, will be null and default to // logged-in user. mStateHolder = new StateHolder( getIntent().getStringExtra(INTENT_EXTRA_USER_ID), getIntent().getStringExtra(INTENT_EXTRA_USER_NAME)); mStateHolder.setRecentOnly(false); } ensureUi(); // Nearby todos is shown first by default so auto-fetch it if necessary. // Nearby is the right button, not the left one, which is a bit strange // but this was a design req. if (!mStateHolder.getRanOnceTodosNearby()) { mStateHolder.startTaskTodos(this, false); } } @Override public void onResume() { super.onResume(); ((Foursquared) getApplication()).requestLocationUpdates(mSearchLocationObserver); } @Override public void onPause() { super.onPause(); ((Foursquared) getApplication()).removeLocationUpdates(mSearchLocationObserver); if (isFinishing()) { mStateHolder.cancelTasks(); mListAdapter.removeObserver(); unregisterReceiver(mLoggedOutReceiver); } } @Override public Object onRetainNonConfigurationInstance() { mStateHolder.setActivity(null); return mStateHolder; } private void ensureUi() { LayoutInflater inflater = LayoutInflater.from(this); setTitle(getString(R.string.todos_activity_title, mStateHolder.getUsername())); mLayoutEmpty = inflater.inflate( R.layout.todos_activity_empty, null); mLayoutEmpty.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT)); mListAdapter = new TodosListAdapter(this, ((Foursquared) getApplication()).getRemoteResourceManager()); if (mStateHolder.getRecentOnly()) { mListAdapter.setGroup(mStateHolder.getTodosRecent()); if (mStateHolder.getTodosRecent().size() == 0) { if (mStateHolder.getRanOnceTodosRecent()) { setEmptyView(mLayoutEmpty); } else { setLoadingView(); } } } else { mListAdapter.setGroup(mStateHolder.getTodosNearby()); if (mStateHolder.getTodosNearby().size() == 0) { if (mStateHolder.getRanOnceTodosNearby()) { setEmptyView(mLayoutEmpty); } else { setLoadingView(); } } } SegmentedButton buttons = getHeaderButton(); buttons.clearButtons(); buttons.addButtons( getString(R.string.todos_activity_btn_recent), getString(R.string.todos_activity_btn_nearby)); if (mStateHolder.getRecentOnly()) { buttons.setPushedButtonIndex(0); } else { buttons.setPushedButtonIndex(1); } buttons.setOnClickListener(new OnClickListenerSegmentedButton() { @Override public void onClick(int index) { if (index == 0) { mStateHolder.setRecentOnly(true); mListAdapter.setGroup(mStateHolder.getTodosRecent()); if (mStateHolder.getTodosRecent().size() < 1) { if (mStateHolder.getRanOnceTodosRecent()) { setEmptyView(mLayoutEmpty); } else { setLoadingView(); mStateHolder.startTaskTodos(TodosActivity.this, true); } } } else { mStateHolder.setRecentOnly(false); mListAdapter.setGroup(mStateHolder.getTodosNearby()); if (mStateHolder.getTodosNearby().size() < 1) { if (mStateHolder.getRanOnceTodosNearby()) { setEmptyView(mLayoutEmpty); } else { setLoadingView(); mStateHolder.startTaskTodos(TodosActivity.this, false); } } } mListAdapter.notifyDataSetChanged(); getListView().setSelection(0); } }); ListView listView = getListView(); listView.setAdapter(mListAdapter); listView.setSmoothScrollbarEnabled(false); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Todo todo = (Todo) parent.getAdapter().getItem(position); if (todo.getTip() != null) { Intent intent = new Intent(TodosActivity.this, TipActivity.class); intent.putExtra(TipActivity.EXTRA_TIP_PARCEL, todo.getTip()); startActivityForResult(intent, ACTIVITY_TIP); } } }); if (mStateHolder.getIsRunningTaskTodosRecent() || mStateHolder.getIsRunningTaskTodosNearby()) { setProgressBarIndeterminateVisibility(true); } else { setProgressBarIndeterminateVisibility(false); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(Menu.NONE, MENU_REFRESH, Menu.NONE, R.string.refresh) .setIcon(R.drawable.ic_menu_refresh); MenuUtils.addPreferencesToMenu(this, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_REFRESH: mStateHolder.startTaskTodos(this, mStateHolder.getRecentOnly()); return true; } return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // We ignore the returned to-do (if any). We search for any to-dos in our // state holder by the linked tip ID for update. if (requestCode == ACTIVITY_TIP && resultCode == Activity.RESULT_OK) { if (data.hasExtra(TipActivity.EXTRA_TIP_RETURNED)) { updateTodo((Tip)data.getParcelableExtra(TipActivity.EXTRA_TIP_RETURNED)); } } } private void updateTodo(Tip tip) { mStateHolder.updateTodo(tip); mListAdapter.notifyDataSetInvalidated(); } private void onStartTaskTodos() { if (mListAdapter != null) { if (mStateHolder.getRecentOnly()) { mStateHolder.setIsRunningTaskTodosRecent(true); mListAdapter.setGroup(mStateHolder.getTodosRecent()); } else { mStateHolder.setIsRunningTaskTodosNearby(true); mListAdapter.setGroup(mStateHolder.getTodosNearby()); } mListAdapter.notifyDataSetChanged(); } setProgressBarIndeterminateVisibility(true); setLoadingView(); } private void onTaskTodosComplete(Group<Todo> group, boolean recentOnly, Exception ex) { SegmentedButton buttons = getHeaderButton(); boolean update = false; if (group != null) { if (recentOnly) { mStateHolder.setTodosRecent(group); if (buttons.getSelectedButtonIndex() == 0) { mListAdapter.setGroup(mStateHolder.getTodosRecent()); update = true; } } else { mStateHolder.setTodosNearby(group); if (buttons.getSelectedButtonIndex() == 1) { mListAdapter.setGroup(mStateHolder.getTodosNearby()); update = true; } } } else { if (recentOnly) { mStateHolder.setTodosRecent(new Group<Todo>()); if (buttons.getSelectedButtonIndex() == 0) { mListAdapter.setGroup(mStateHolder.getTodosRecent()); update = true; } } else { mStateHolder.setTodosNearby(new Group<Todo>()); if (buttons.getSelectedButtonIndex() == 1) { mListAdapter.setGroup(mStateHolder.getTodosNearby()); update = true; } } NotificationsUtil.ToastReasonForFailure(this, ex); } if (recentOnly) { mStateHolder.setIsRunningTaskTodosRecent(false); mStateHolder.setRanOnceTodosRecent(true); if (mStateHolder.getTodosRecent().size() == 0 && buttons.getSelectedButtonIndex() == 0) { setEmptyView(mLayoutEmpty); } } else { mStateHolder.setIsRunningTaskTodosNearby(false); mStateHolder.setRanOnceTodosNearby(true); if (mStateHolder.getTodosNearby().size() == 0 && buttons.getSelectedButtonIndex() == 1) { setEmptyView(mLayoutEmpty); } } if (update) { mListAdapter.notifyDataSetChanged(); getListView().setSelection(0); } if (!mStateHolder.getIsRunningTaskTodosRecent() && !mStateHolder.getIsRunningTaskTodosNearby()) { setProgressBarIndeterminateVisibility(false); } } /** * Gets friends of the current user we're working for. */ private static class TaskTodos extends AsyncTask<Void, Void, Group<Todo>> { private String mUserId; private TodosActivity mActivity; private boolean mRecentOnly; private Exception mReason; public TaskTodos(TodosActivity activity, String userId, boolean friendsOnly) { mActivity = activity; mUserId = userId; mRecentOnly = friendsOnly; } @Override protected void onPreExecute() { mActivity.onStartTaskTodos(); } @Override protected Group<Todo> doInBackground(Void... params) { try { Foursquared foursquared = (Foursquared) mActivity.getApplication(); Foursquare foursquare = foursquared.getFoursquare(); Location loc = foursquared.getLastKnownLocation(); if (loc == null) { try { Thread.sleep(3000); } catch (InterruptedException ex) {} loc = foursquared.getLastKnownLocation(); if (loc == null) { throw new FoursquareException("Your location could not be determined!"); } } return foursquare.todos( LocationUtils.createFoursquareLocation(loc), mUserId, mRecentOnly, !mRecentOnly, 30); } catch (Exception e) { mReason = e; } return null; } @Override protected void onPostExecute(Group<Todo> todos) { if (mActivity != null) { mActivity.onTaskTodosComplete(todos, mRecentOnly, mReason); } } @Override protected void onCancelled() { if (mActivity != null) { mActivity.onTaskTodosComplete(null, mRecentOnly, mReason); } } public void setActivity(TodosActivity activity) { mActivity = activity; } } private static class StateHolder { private Group<Todo> mTodosRecent; private Group<Todo> mTodosNearby; private boolean mIsRunningTaskTodosRecent; private boolean mIsRunningTaskTodosNearby; private boolean mRecentOnly; private boolean mRanOnceTodosRecent; private boolean mRanOnceTodosNearby; private TaskTodos mTaskTodosRecent; private TaskTodos mTaskTodosNearby; private String mUserId; private String mUsername; public StateHolder(String userId, String username) { mIsRunningTaskTodosRecent = false; mIsRunningTaskTodosNearby = false; mRanOnceTodosRecent = false; mRanOnceTodosNearby = false; mTodosRecent = new Group<Todo>(); mTodosNearby = new Group<Todo>(); mRecentOnly = false; mUserId = userId; mUsername = username; } public String getUsername() { return mUsername; } public Group<Todo> getTodosRecent() { return mTodosRecent; } public void setTodosRecent(Group<Todo> todosRecent) { mTodosRecent = todosRecent; } public Group<Todo> getTodosNearby() { return mTodosNearby; } public void setTodosNearby(Group<Todo> todosNearby) { mTodosNearby = todosNearby; } public void startTaskTodos(TodosActivity activity, boolean recentOnly) { if (recentOnly) { if (mIsRunningTaskTodosRecent) { return; } mIsRunningTaskTodosRecent = true; mTaskTodosRecent = new TaskTodos(activity, mUserId, recentOnly); mTaskTodosRecent.execute(); } else { if (mIsRunningTaskTodosNearby) { return; } mIsRunningTaskTodosNearby = true; mTaskTodosNearby = new TaskTodos(activity, mUserId, recentOnly); mTaskTodosNearby.execute(); } } public void setActivity(TodosActivity activity) { if (mTaskTodosRecent != null) { mTaskTodosRecent.setActivity(activity); } if (mTaskTodosNearby != null) { mTaskTodosNearby.setActivity(activity); } } public boolean getIsRunningTaskTodosRecent() { return mIsRunningTaskTodosRecent; } public void setIsRunningTaskTodosRecent(boolean isRunning) { mIsRunningTaskTodosRecent = isRunning; } public boolean getIsRunningTaskTodosNearby() { return mIsRunningTaskTodosNearby; } public void setIsRunningTaskTodosNearby(boolean isRunning) { mIsRunningTaskTodosNearby = isRunning; } public void cancelTasks() { if (mTaskTodosRecent != null) { mTaskTodosRecent.setActivity(null); mTaskTodosRecent.cancel(true); } if (mTaskTodosNearby != null) { mTaskTodosNearby.setActivity(null); mTaskTodosNearby.cancel(true); } } public boolean getRecentOnly() { return mRecentOnly; } public void setRecentOnly(boolean recentOnly) { mRecentOnly = recentOnly; } public boolean getRanOnceTodosRecent() { return mRanOnceTodosRecent; } public void setRanOnceTodosRecent(boolean ranOnce) { mRanOnceTodosRecent = ranOnce; } public boolean getRanOnceTodosNearby() { return mRanOnceTodosNearby; } public void setRanOnceTodosNearby(boolean ranOnce) { mRanOnceTodosNearby = ranOnce; } public void updateTodo(Tip tip) { updateTodoFromArray(tip, mTodosRecent); updateTodoFromArray(tip, mTodosNearby); } private void updateTodoFromArray(Tip tip, Group<Todo> target) { for (int i = 0, m = target.size(); i < m; i++) { Todo todo = target.get(i); if (todo.getTip() != null) { // Fix for old garbage todos/tips from the API. if (todo.getTip().getId().equals(tip.getId())) { if (mUserId == null) { // Activity is operating on logged-in user, only removing todos // from the list, don't have to worry about updating states. if (!TipUtils.isTodo(tip)) { target.remove(todo); } } else { // Activity is operating on another user, so just update the status // of the tip within the todo. todo.getTip().setStatus(tip.getStatus()); } break; } } } } } /** * This is really just a dummy observer to get the GPS running * since this is the new splash page. After getting a fix, we * might want to stop registering this observer thereafter so * it doesn't annoy the user too much. */ private class SearchLocationObserver implements Observer { @Override public void update(Observable observable, Object data) { } } }