/**
* Copyright 2009 Joe LaPenna
*/
package com.joelapenna.foursquared;
import com.joelapenna.foursquare.Foursquare;
import com.joelapenna.foursquare.types.Category;
import com.joelapenna.foursquare.types.Group;
import com.joelapenna.foursquare.types.Response;
import com.joelapenna.foursquare.types.Venue;
import com.joelapenna.foursquared.location.LocationUtils;
import com.joelapenna.foursquared.util.NotificationsUtil;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
/**
* Allows the user to add a new venue. This activity can also be used to submit
* edits to an existing venue. Pass a venue parcelable using the EXTRA_VENUE_TO_EDIT
* key to put the activity into edit mode.
*
* @author Joe LaPenna (joe@joelapenna.com)
* @author Mark Wyszomierski (markww@gmail.com)
* -Added support for using this activity to edit existing venues (June 8, 2010).
*/
public class AddVenueActivity extends Activity {
private static final String TAG = "AddVenueActivity";
private static final boolean DEBUG = FoursquaredSettings.DEBUG;
public static final String EXTRA_VENUE_TO_EDIT = "com.joelapenna.foursquared.VenueParcel";
private static final double MINIMUM_ACCURACY_FOR_ADDRESS = 100.0;
private static final int DIALOG_PICK_CATEGORY = 1;
private static final int DIALOG_ERROR = 2;
private StateHolder mStateHolder;
private EditText mNameEditText;
private EditText mAddressEditText;
private EditText mCrossstreetEditText;
private EditText mCityEditText;
private EditText mStateEditText;
private EditText mZipEditText;
private EditText mPhoneEditText;
private Button mAddOrEditVenueButton;
private LinearLayout mCategoryLayout;
private ImageView mCategoryImageView;
private TextView mCategoryTextView;
private ProgressBar mCategoryProgressBar;
private ProgressDialog mDlgProgress;
private TextWatcher mNameFieldWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mAddOrEditVenueButton.setEnabled(canEnableSaveButton());
}
};
private BroadcastReceiver mLoggedOutReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "onReceive: " + intent);
finish();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate()");
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.add_venue_activity);
registerReceiver(mLoggedOutReceiver, new IntentFilter(Foursquared.INTENT_ACTION_LOGGED_OUT));
mAddOrEditVenueButton = (Button) findViewById(R.id.addVenueButton);
mNameEditText = (EditText) findViewById(R.id.nameEditText);
mAddressEditText = (EditText) findViewById(R.id.addressEditText);
mCrossstreetEditText = (EditText) findViewById(R.id.crossstreetEditText);
mCityEditText = (EditText) findViewById(R.id.cityEditText);
mStateEditText = (EditText) findViewById(R.id.stateEditText);
mZipEditText = (EditText) findViewById(R.id.zipEditText);
mPhoneEditText = (EditText) findViewById(R.id.phoneEditText);
mCategoryLayout = (LinearLayout) findViewById(R.id.addVenueCategoryLayout);
mCategoryImageView = (ImageView) findViewById(R.id.addVenueCategoryIcon);
mCategoryTextView = (TextView) findViewById(R.id.addVenueCategoryTextView);
mCategoryProgressBar = (ProgressBar) findViewById(R.id.addVenueCategoryProgressBar);
mCategoryLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
showDialog(DIALOG_PICK_CATEGORY);
}
});
mCategoryLayout.setEnabled(false);
mAddOrEditVenueButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String name = mNameEditText.getText().toString();
String address = mAddressEditText.getText().toString();
String crossstreet = mCrossstreetEditText.getText().toString();
String city = mCityEditText.getText().toString();
String state = mStateEditText.getText().toString();
String zip = mZipEditText.getText().toString();
String phone = mPhoneEditText.getText().toString();
if (mStateHolder.getVenueBeingEdited() != null) {
if (TextUtils.isEmpty(name)) {
showDialogError(getResources().getString(
R.string.add_venue_activity_error_no_venue_name));
return;
} else if (TextUtils.isEmpty(address)) {
showDialogError(getResources().getString(
R.string.add_venue_activity_error_no_venue_address));
return;
} else if (TextUtils.isEmpty(city)) {
showDialogError(getResources().getString(
R.string.add_venue_activity_error_no_venue_city));
return;
} else if (TextUtils.isEmpty(state)) {
showDialogError(getResources().getString(
R.string.add_venue_activity_error_no_venue_state));
return;
}
}
mStateHolder.startTaskAddOrEditVenue(
AddVenueActivity.this,
new String[] {
name,
address,
crossstreet,
city,
state,
zip,
phone,
mStateHolder.getChosenCategory() != null ?
mStateHolder.getChosenCategory().getId() : ""
},
// If editing a venue, pass in its id.
mStateHolder.getVenueBeingEdited() != null ?
mStateHolder.getVenueBeingEdited().getId() : null);
}
});
mNameEditText.addTextChangedListener(mNameFieldWatcher);
Object retained = getLastNonConfigurationInstance();
if (retained != null && retained instanceof StateHolder) {
mStateHolder = (StateHolder) retained;
mStateHolder.setActivity(this);
setFields(mStateHolder.getAddressLookup());
setChosenCategory(mStateHolder.getChosenCategory());
if (mStateHolder.getCategories() != null && mStateHolder.getCategories().size() > 0) {
mCategoryLayout.setEnabled(true);
mCategoryProgressBar.setVisibility(View.GONE);
}
} else {
mStateHolder = new StateHolder();
mStateHolder.startTaskGetCategories(this);
// If passed the venue parcelable, then we are in 'edit' mode.
if (getIntent().getExtras() != null && getIntent().getExtras().containsKey(EXTRA_VENUE_TO_EDIT)) {
Venue venue = getIntent().getExtras().getParcelable(EXTRA_VENUE_TO_EDIT);
if (venue != null) {
mStateHolder.setVenueBeingEdited(venue);
setFields(venue);
setTitle(getResources().getString(R.string.add_venue_activity_label_edit_venue));
mAddOrEditVenueButton.setText(getResources().getString(
R.string.add_venue_activity_btn_submit_edits));
} else {
Log.e(TAG, "Null venue parcelable supplied at startup, will finish immediately.");
finish();
}
} else {
mStateHolder.startTaskAddressLookup(this);
}
}
}
@Override
public void onResume() {
super.onResume();
((Foursquared) getApplication()).requestLocationUpdates(true);
if (mStateHolder.getIsRunningTaskAddOrEditVenue()) {
startProgressBar();
}
}
@Override
public void onPause() {
super.onPause();
((Foursquared) getApplication()).removeLocationUpdates();
stopProgressBar();
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mLoggedOutReceiver);
}
@Override
public Object onRetainNonConfigurationInstance() {
mStateHolder.setActivity(null);
return mStateHolder;
}
private void showDialogError(String message) {
mStateHolder.setError(message);
showDialog(DIALOG_ERROR);
}
/**
* Set fields from an address lookup, only used when adding a venue. This is done
* to prepopulate some fields for the user.
*/
private void setFields(AddressLookup addressLookup) {
if (mStateHolder.getVenueBeingEdited() == null &&
addressLookup != null &&
addressLookup.getAddress() != null) {
// Don't fill in the street unless we're reasonably confident we
// know where the user is.
String address = addressLookup.getAddress().getAddressLine(0);
double accuracy = addressLookup.getLocation().getAccuracy();
if (address != null && (accuracy > 0.0 && accuracy < MINIMUM_ACCURACY_FOR_ADDRESS)) {
if (DEBUG) Log.d(TAG, "Accuracy good enough, setting address field.");
mAddressEditText.setText(address);
}
String city = addressLookup.getAddress().getLocality();
if (city != null) {
mCityEditText.setText(city);
}
String state = addressLookup.getAddress().getAdminArea();
if (state != null) {
mStateEditText.setText(state);
}
String zip = addressLookup.getAddress().getPostalCode();
if (zip != null) {
mZipEditText.setText(zip);
}
String phone = addressLookup.getAddress().getPhone();
if (phone != null) {
mPhoneEditText.setText(phone);
}
}
}
/**
* Set fields from an existing venue, this is only used when editing a venue.
*/
private void setFields(Venue venue) {
mNameEditText.setText(venue.getName());
mCrossstreetEditText.setText(venue.getCrossstreet());
mAddressEditText.setText(venue.getAddress());
mCityEditText.setText(venue.getCity());
mStateEditText.setText(venue.getState());
mZipEditText.setText(venue.getZip());
mPhoneEditText.setText(venue.getPhone());
}
private void startProgressBar() {
startProgressBar(
getResources().getString(
mStateHolder.getVenueBeingEdited() == null ?
R.string.add_venue_progress_bar_title_add_venue :
R.string.add_venue_progress_bar_title_edit_venue),
getResources().getString(
mStateHolder.getVenueBeingEdited() == null ?
R.string.add_venue_progress_bar_message_add_venue :
R.string.add_venue_progress_bar_message_edit_venue));
}
private void startProgressBar(String title, String message) {
if (mDlgProgress == null) {
mDlgProgress = ProgressDialog.show(this, title, message);
}
mDlgProgress.show();
}
private void stopProgressBar() {
if (mDlgProgress != null) {
mDlgProgress.dismiss();
mDlgProgress = null;
}
}
private void onGetCategoriesTaskComplete(Group<Category> categories, Exception ex) {
mStateHolder.setIsRunningTaskGetCategories(false);
try {
// Populate the categories list now.
if (categories != null) {
mStateHolder.setCategories(categories);
mCategoryLayout.setEnabled(true);
mCategoryTextView.setText(getResources().getString(R.string.add_venue_activity_pick_category_label));
mCategoryProgressBar.setVisibility(View.GONE);
// If we are editing a venue, set its category here.
if (mStateHolder.getVenueBeingEdited() != null) {
Venue venue = mStateHolder.getVenueBeingEdited();
if (venue.getCategory() != null) {
setChosenCategory(venue.getCategory());
}
}
} else {
// If error, feed list adapter empty user group.
mStateHolder.setCategories(new Group<Category>());
NotificationsUtil.ToastReasonForFailure(this, ex);
}
} finally {
}
stopIndeterminateProgressBar();
}
private void ooGetAddressLookupTaskComplete(AddressLookup addressLookup, Exception ex) {
mStateHolder.setIsRunningTaskAddressLookup(false);
try {
// We can prepopulate some of the fields for them now.
if (addressLookup != null) {
mStateHolder.setAddressLookup(addressLookup);
setFields(addressLookup);
} else {
NotificationsUtil.ToastReasonForFailure(this, ex);
}
} finally {
}
stopIndeterminateProgressBar();
}
private void onAddOrEditVenueTaskComplete(Venue venue, String venueIdIfEditing, Exception ex) {
mStateHolder.setIsRunningTaskAddOrEditVenue(false);
stopProgressBar();
if (venueIdIfEditing == null) {
if (venue != null) {
// If they added the venue ok, then send them to an activity displaying it
// so they can play around with it.
Intent intent = new Intent(AddVenueActivity.this, VenueActivity.class);
intent.putExtra(Foursquared.EXTRA_VENUE_ID, venue.getId());
startActivity(intent);
finish();
} else {
// Error, let them hang out here.
NotificationsUtil.ToastReasonForFailure(this, ex);
}
} else {
if (venue != null) {
// Editing the venue worked ok, just return to caller.
Toast.makeText(this, getResources().getString(
R.string.add_venue_activity_edit_venue_success),
Toast.LENGTH_SHORT).show();
finish();
} else {
// Error, let them hang out here.
NotificationsUtil.ToastReasonForFailure(this, ex);
}
}
}
private void stopIndeterminateProgressBar() {
if (mStateHolder.getIsRunningTaskAddressLookup() == false &&
mStateHolder.getIsRunningTaskGetCategories() == false) {
setProgressBarIndeterminateVisibility(false);
}
}
private static class AddOrEditVenueTask extends AsyncTask<Void, Void, Venue> {
private AddVenueActivity mActivity;
private String[] mParams;
private String mVenueIdIfEditing;
private Exception mReason;
private Foursquared mFoursquared;
private String mErrorMsgForEditVenue;
public AddOrEditVenueTask(AddVenueActivity activity,
String[] params,
String venueIdIfEditing) {
mActivity = activity;
mParams = params;
mVenueIdIfEditing = venueIdIfEditing;
mFoursquared = (Foursquared) activity.getApplication();
mErrorMsgForEditVenue = activity.getResources().getString(
R.string.add_venue_activity_edit_venue_fail);
}
public void setActivity(AddVenueActivity activity) {
mActivity = activity;
}
@Override
protected void onPreExecute() {
mActivity.startProgressBar();
}
@Override
protected Venue doInBackground(Void... params) {
try {
Foursquare foursquare = mFoursquared.getFoursquare();
Location location = mFoursquared.getLastKnownLocationOrThrow();
if (mVenueIdIfEditing == null) {
return foursquare.addVenue(
mParams[0], // name
mParams[1], // address
mParams[2], // cross street
mParams[3], // city
mParams[4], // state,
mParams[5], // zip
mParams[6], // phone
mParams[7], // category id
LocationUtils.createFoursquareLocation(location));
} else {
Response response =
foursquare.proposeedit(
mVenueIdIfEditing,
mParams[0], // name
mParams[1], // address
mParams[2], // cross street
mParams[3], // city
mParams[4], // state,
mParams[5], // zip
mParams[6], // phone
mParams[7], // category id
LocationUtils.createFoursquareLocation(location));
if (response != null && response.getValue().equals("ok")) {
// TODO: Come up with a better method than returning an empty venue on success.
return new Venue();
} else {
throw new Exception(mErrorMsgForEditVenue);
}
}
} catch (Exception e) {
Log.e(TAG, "Exception during add or edit venue.", e);
mReason = e;
}
return null;
}
@Override
protected void onPostExecute(Venue venue) {
if (DEBUG) Log.d(TAG, "onPostExecute()");
if (mActivity != null) {
mActivity.onAddOrEditVenueTaskComplete(venue, mVenueIdIfEditing, mReason);
}
}
@Override
protected void onCancelled() {
if (mActivity != null) {
mActivity.onAddOrEditVenueTaskComplete(null, mVenueIdIfEditing, mReason);
}
}
}
private static class AddressLookupTask extends AsyncTask<Void, Void, AddressLookup> {
private AddVenueActivity mActivity;
private Exception mReason;
public AddressLookupTask(AddVenueActivity activity) {
mActivity = activity;
}
public void setActivity(AddVenueActivity activity) {
mActivity = activity;
}
@Override
protected void onPreExecute() {
mActivity.setProgressBarIndeterminateVisibility(true);
}
@Override
protected AddressLookup doInBackground(Void... params) {
try {
Location location = ((Foursquared)mActivity.getApplication()).getLastKnownLocationOrThrow();
Geocoder geocoder = new Geocoder(mActivity);
return new AddressLookup(
location,
geocoder.getFromLocation(
location.getLatitude(),
location.getLongitude(), 1).get(0));
} catch (Exception e) {
mReason = e;
}
return null;
}
@Override
protected void onPostExecute(AddressLookup address) {
if (mActivity != null) {
mActivity.ooGetAddressLookupTaskComplete(address, mReason);
}
}
@Override
protected void onCancelled() {
if (mActivity != null) {
mActivity.ooGetAddressLookupTaskComplete(null, mReason);
}
}
}
private static class GetCategoriesTask extends AsyncTask<Void, Void, Group<Category>> {
private AddVenueActivity mActivity;
private Exception mReason;
public GetCategoriesTask(AddVenueActivity activity) {
mActivity = activity;
}
public void setActivity(AddVenueActivity activity) {
mActivity = activity;
}
@Override
protected void onPreExecute() {
mActivity.setProgressBarIndeterminateVisibility(true);
}
@Override
protected Group<Category> doInBackground(Void... params) {
try {
Foursquared foursquared = (Foursquared) mActivity.getApplication();
Foursquare foursquare = foursquared.getFoursquare();
return foursquare.categories();
} catch (Exception e) {
if (DEBUG)
Log.d(TAG, "GetCategoriesTask: Exception doing send friend request.", e);
mReason = e;
}
return null;
}
@Override
protected void onPostExecute(Group<Category> categories) {
if (DEBUG) Log.d(TAG, "GetCategoriesTask: onPostExecute()");
if (mActivity != null) {
mActivity.onGetCategoriesTaskComplete(categories, mReason);
}
}
@Override
protected void onCancelled() {
if (mActivity != null) {
mActivity.onGetCategoriesTaskComplete(null,
new Exception("Get categories task request cancelled."));
}
}
}
private static class StateHolder {
private AddressLookupTask mTaskGetAddress;
private AddressLookup mAddressLookup;
private boolean mIsRunningTaskAddressLookup;
private GetCategoriesTask mTaskGetCategories;
private Group<Category> mCategories;
private boolean mIsRunningTaskGetCategories;
private AddOrEditVenueTask mTaskAddOrEditVenue;
private boolean mIsRunningTaskAddOrEditVenue;
private Category mChosenCategory;
private Venue mVenueBeingEdited;
private String mError;
public StateHolder() {
mCategories = new Group<Category>();
mIsRunningTaskAddressLookup = false;
mIsRunningTaskGetCategories = false;
mIsRunningTaskAddOrEditVenue = false;
mVenueBeingEdited = null;
}
public void setCategories(Group<Category> categories) {
mCategories = categories;
}
public void setAddressLookup(AddressLookup addressLookup) {
mAddressLookup = addressLookup;
}
public Group<Category> getCategories() {
return mCategories;
}
public AddressLookup getAddressLookup() {
return mAddressLookup;
}
public Venue getVenueBeingEdited() {
return mVenueBeingEdited;
}
public void setVenueBeingEdited(Venue venue) {
mVenueBeingEdited = venue;
}
public void startTaskGetCategories(AddVenueActivity activity) {
mIsRunningTaskGetCategories = true;
mTaskGetCategories = new GetCategoriesTask(activity);
mTaskGetCategories.execute();
}
public void startTaskAddressLookup(AddVenueActivity activity) {
mIsRunningTaskAddressLookup = true;
mTaskGetAddress = new AddressLookupTask(activity);
mTaskGetAddress.execute();
}
public void startTaskAddOrEditVenue(AddVenueActivity activity, String[] params,
String venueIdIfEditing) {
mIsRunningTaskAddOrEditVenue = true;
mTaskAddOrEditVenue = new AddOrEditVenueTask(activity, params, venueIdIfEditing);
mTaskAddOrEditVenue.execute();
}
public void setActivity(AddVenueActivity activity) {
if (mTaskGetCategories != null) {
mTaskGetCategories.setActivity(activity);
}
if (mTaskGetAddress != null) {
mTaskGetAddress.setActivity(activity);
}
if (mTaskAddOrEditVenue != null) {
mTaskAddOrEditVenue.setActivity(activity);
}
}
public void setIsRunningTaskAddressLookup(boolean isRunning) {
mIsRunningTaskAddressLookup = isRunning;
}
public void setIsRunningTaskGetCategories(boolean isRunning) {
mIsRunningTaskGetCategories = isRunning;
}
public void setIsRunningTaskAddOrEditVenue(boolean isRunning) {
mIsRunningTaskAddOrEditVenue = isRunning;
}
public boolean getIsRunningTaskAddressLookup() {
return mIsRunningTaskAddressLookup;
}
public boolean getIsRunningTaskGetCategories() {
return mIsRunningTaskGetCategories;
}
public boolean getIsRunningTaskAddOrEditVenue() {
return mIsRunningTaskAddOrEditVenue;
}
public Category getChosenCategory() {
return mChosenCategory;
}
public void setChosenCategory(Category category) {
mChosenCategory = category;
}
public String getError() {
return mError;
}
public void setError(String error) {
mError = error;
}
}
private static class AddressLookup {
private Location mLocation;
private Address mAddress;
public AddressLookup(Location location, Address address) {
mLocation = location;
mAddress = address;
}
public Location getLocation() {
return mLocation;
}
public Address getAddress() {
return mAddress;
}
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_PICK_CATEGORY:
// When the user cancels the dialog (by hitting the 'back' key), we
// finish this activity. We don't listen to onDismiss() for this
// action, because a device rotation will fire onDismiss(), and our
// dialog would not be re-displayed after the rotation is complete.
CategoryPickerDialog dlg = new CategoryPickerDialog(
this,
mStateHolder.getCategories(),
((Foursquared)getApplication()));
dlg.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
CategoryPickerDialog dlg = (CategoryPickerDialog)dialog;
setChosenCategory(dlg.getChosenCategory());
removeDialog(DIALOG_PICK_CATEGORY);
}
});
return dlg;
case DIALOG_ERROR:
AlertDialog dlgInfo = new AlertDialog.Builder(this)
.setIcon(0)
.setTitle(getResources().getString(R.string.add_venue_progress_bar_title_edit_venue))
.setMessage(mStateHolder.getError()).create();
dlgInfo.setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
removeDialog(DIALOG_ERROR);
}
});
return dlgInfo;
}
return null;
}
private void setChosenCategory(Category category) {
if (category == null) {
mCategoryTextView.setText(getResources().getString(
R.string.add_venue_activity_pick_category_label));
return;
}
try {
Bitmap bitmap = BitmapFactory.decodeStream(
((Foursquared)getApplication()).getRemoteResourceManager().getInputStream(
Uri.parse(category.getIconUrl())));
mCategoryImageView.setImageBitmap(bitmap);
} catch (IOException e) {
if (DEBUG) Log.e(TAG, "Error loading category icon.", e);
}
mCategoryTextView.setText(category.getNodeName());
// Record the chosen category.
mStateHolder.setChosenCategory(category);
if (canEnableSaveButton()) {
mAddOrEditVenueButton.setEnabled(canEnableSaveButton());
}
}
private boolean canEnableSaveButton() {
return mNameEditText.getText().length() > 0;
}
}