/*
* Copyright 2012 The Stanford MobiSocial Laboratory
*
* 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 mobisocial.musubi.ui;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
import mobisocial.metrics.MusubiMetrics;
import mobisocial.musubi.App;
import mobisocial.musubi.Helpers;
import mobisocial.musubi.MembersActivity.FeedMembersFragment.FeedMembersCursorLoader;
import mobisocial.musubi.R;
import mobisocial.musubi.model.MFeed;
import mobisocial.musubi.model.MIdentity;
import mobisocial.musubi.model.helpers.DatabaseManager;
import mobisocial.musubi.model.helpers.FeedManager;
import mobisocial.musubi.model.helpers.IdentitiesManager;
import mobisocial.musubi.model.helpers.MyAccountManager;
import mobisocial.musubi.nearby.GpsBroadcastTask;
import mobisocial.musubi.objects.FeedNameObj;
import mobisocial.musubi.objects.IntroductionObj;
import mobisocial.musubi.ui.util.EmojiSpannableFactory;
import mobisocial.musubi.ui.util.UiUtil;
import mobisocial.musubi.ui.widget.ActionBarLite;
import mobisocial.musubi.ui.widget.MultiIdentitySelector;
import mobisocial.musubi.ui.widget.MultiIdentitySelector.OnRequestAddIdentityListener;
import mobisocial.musubi.util.CommonLayouts;
import mobisocial.musubi.util.InstrumentedActivity;
import mobisocial.musubi.util.PhotoTaker;
import mobisocial.musubi.util.UriImage;
import mobisocial.socialkit.Obj;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Intents.Insert;
import android.provider.MediaStore;
import android.provider.Settings;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.SupportActivity;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.Spannable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class FeedDetailsActivity extends MusubiBaseActivity implements OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
DatabaseManager mDb;
private static final int sDeletedColor = Color.parseColor("#66FF3333");
boolean mDetailsChanged = false;
byte[] mThumbnailBytes;
ImageView mThumbnailView;
EditText mNameEditText;
EditText mBroadcastPassword;
ListView mFeedMembersView;
Context mContext;
IdentitiesManager mIdentitiesManager;
ContactListCursorAdapter mContacts;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.feed_details);
setTitle("Feed Details");
mDb = new DatabaseManager(this);
SQLiteOpenHelper helper = App.getDatabaseSource(this);
mIdentitiesManager = new IdentitiesManager(helper);
mContacts = new ContactListCursorAdapter(this, null);
mFeedMembersView = (ListView) findViewById(R.id.feed_details_members_list);
LayoutInflater inflater = getLayoutInflater();
ViewGroup header = (ViewGroup)inflater.inflate(R.layout.feed_details_header, mFeedMembersView, false);
mFeedMembersView.addHeaderView(header, null, false);
mFeedMembersView.setTextFilterEnabled(true);
mFeedMembersView.setFastScrollEnabled(true);
mFeedMembersView.setOnItemClickListener(this);
mFeedMembersView.setCacheColorHint(Color.WHITE);
mFeedMembersView.setAdapter(mContacts);
mNameEditText = (EditText)header.findViewById(R.id.feed_title_edittext);
mBroadcastPassword = (EditText)header.findViewById(R.id.broadcast_password);
mThumbnailView = (ImageView)header.findViewById(R.id.icon);
String name = null;
if (savedInstanceState != null) {
name = savedInstanceState.getString("name");
mThumbnailBytes = savedInstanceState.getByteArray("thumbnailBytes");
mDetailsChanged = savedInstanceState.getBoolean("detailsChanged");
} else {
Long feedId = Long.parseLong(getIntent().getData().getLastPathSegment());
MFeed feed = mDb.getFeedManager().lookupFeed(feedId);
name = UiUtil.getFeedNameFromMembersList(mDb.getFeedManager(), feed);
mThumbnailBytes = mDb.getFeedManager().getFeedThumbnailForId(feedId);
}
Spannable span = EmojiSpannableFactory.getInstance(this).newSpannable(name);
mNameEditText.setText(span);
mNameEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mDetailsChanged = true;
refreshUI();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
EmojiSpannableFactory.getInstance(mContext).updateSpannable(s);
}
});
getSupportLoaderManager().initLoader(0, null, this);
refreshUI();
}
public void onClickAddMembers(View v) {
((InstrumentedActivity)this).showDialog(
AddPeopleDialog.newInstance(getIntent().getData()));
}
public void onClickIcon(View v) {
SelectImageDialogFragment sidf = SelectImageDialogFragment.newInstance();
showDialog(sidf);
}
public void onClickBroadcast(View v) {
long id = Long.parseLong(getIntent().getData().getLastPathSegment());
FeedManager fm = new FeedManager(App.getDatabaseSource(this));
final MFeed f = fm.lookupFeed(id);
String provider = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
if (provider == null || !provider.contains("gps") || !provider.contains("network")) {
new AlertDialog.Builder(this)
.setTitle("Location Settings")
.setMessage("You should enable both network-based and GPS-based location services to ensure your friends can find your groups.")
.setNegativeButton("Share Anyway", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new GpsBroadcastTask(mContext, f, mBroadcastPassword.getText().toString()).execute();
}
})
.setPositiveButton("Fix Settings First", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
Intent myIntent = new Intent( Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(myIntent);
} catch(Throwable t) { Log.e(TAG, "failed to launch location settings", t);}
}
}).show();
} else {
new GpsBroadcastTask(mContext, f, mBroadcastPassword.getText().toString()).execute();
}
}
@Override
public void onBackPressed() {
onClickSave(null);
}
public void onClickSave(View v) {
if (mDetailsChanged) {
String name = mNameEditText.getText().toString();
Obj obj = FeedNameObj.from(name, mThumbnailBytes);
Helpers.sendToFeed(this, obj, getIntent().getData());
}
finish();
}
public void onClickCancel(View v) {
finish();
}
public void refreshUI() {
if (mDetailsChanged) {
findViewById(R.id.submit).setVisibility(View.VISIBLE);
findViewById(R.id.cancel).setVisibility(View.VISIBLE);
} else {
findViewById(R.id.submit).setVisibility(View.GONE);
findViewById(R.id.cancel).setVisibility(View.GONE);
}
if (mThumbnailBytes != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(mThumbnailBytes, 0, mThumbnailBytes.length);
mThumbnailView.setImageBitmap(bitmap);
}
else {
mThumbnailView.setImageResource(R.drawable.group_icon);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("name", mNameEditText.getText().toString());
outState.putByteArray("thumbnailBytes", mThumbnailBytes);
outState.putBoolean("detailsChanged", mDetailsChanged);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
if (SelectImageDialogFragment.isImageRequest(requestCode)) {
new SelectImageDialogFragment.ThumbnailAsyncTask(this, requestCode, data) {
protected void onPreExecute() {
};
@Override
protected void onThumbnailResult(byte[] imageBytes) {
if (imageBytes != null) {
mThumbnailBytes = imageBytes;
mDetailsChanged = true;
refreshUI();
} else {
toast("Failed to load image");
}
};
}.execute();
}
}
public static class SelectImageDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
public static final int REQUEST_CAMERA_IMAGE = 58;
public static final int REQUEST_GALLERY_IMAGE = 59;
static final int SOURCE_CAMERA = 0;
static final int SOURCE_GALLERY = 1;
public static SelectImageDialogFragment newInstance() {
Bundle args = new Bundle();
SelectImageDialogFragment sidf = new SelectImageDialogFragment();
sidf.setArguments(args);
return sidf;
}
public static boolean isImageRequest(int requestCode) {
return (requestCode == REQUEST_CAMERA_IMAGE || requestCode == REQUEST_GALLERY_IMAGE);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String[] sources = new String[] { "From Camera", "From Gallery" };
return new AlertDialog.Builder(getActivity())
.setTitle("Choose an image...")
.setItems(sources, this)
.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case SOURCE_CAMERA:
final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(PhotoTaker.getTempFile(getActivity())));
getActivity().startActivityForResult(intent, REQUEST_CAMERA_IMAGE);
break;
case SOURCE_GALLERY:
Intent gallery = new Intent(Intent.ACTION_GET_CONTENT);
gallery.setType("image/*");
getActivity().startActivityForResult(gallery, REQUEST_GALLERY_IMAGE);
break;
}
}
public static abstract class ThumbnailAsyncTask extends AsyncTask<Void, Void, byte[]> {
final Context mContext;
final int mRequestCode;
final Intent mData;
public ThumbnailAsyncTask(Context context, int requestCode, Intent data) {
mContext = context;
mRequestCode = requestCode;
mData = data;
}
@Override
protected byte[] doInBackground(Void... params) {
Uri imageUri;
switch (mRequestCode) {
case REQUEST_CAMERA_IMAGE:
File imageFile = PhotoTaker.getTempFile(mContext);
imageUri = Uri.fromFile(imageFile);
break;
case REQUEST_GALLERY_IMAGE:
if (mData != null) {
imageUri = mData.getData();
} else {
imageUri = null;
}
break;
default:
imageUri = null;
}
if (imageUri != null) {
UriImage uriImage = new UriImage(mContext, imageUri);
try {
return uriImage.getResizedImageData(300, 300, 20*1024, true);
} catch (IOException e) {
Log.e(TAG, "Error decoding image", e);
return null;
}
} else {
Log.e(TAG, "bad request code");
return null;
}
}
@Override
protected final void onPostExecute(byte[] result) {
onThumbnailResult(result);
}
protected abstract void onThumbnailResult(byte[] imageBytes);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
refreshUI();
}
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
long feedId = Long.parseLong(getIntent().getData().getLastPathSegment());
return new FeedMembersCursorLoader(this, feedId);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mContacts.changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id){
Cursor cursor = (Cursor)mContacts.getItem(position-1);
long identityId = cursor.getLong(cursor.getColumnIndexOrThrow(MIdentity.COL_ID));
Intent intent = new Intent(this, ViewProfileActivity.class);
intent.putExtra(ViewProfileActivity.PROFILE_ID, identityId);
this.startActivity(intent);
}
private class ContactListCursorAdapter extends CursorAdapter {
public ContactListCursorAdapter (Context context, Cursor c) {
super(context, c);
}
@Override
public View newView(Context context, Cursor c, ViewGroup parent) {
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.contacts_item, parent, false);
bindView(v, context, c);
return v;
}
@Override
public void bindView(View v, Context context, Cursor cursor) {
TextView unreadCount = (TextView)v.findViewById(R.id.unread_count);
unreadCount.setVisibility(View.GONE);
TextView nameText = (TextView) v.findViewById(R.id.name_text);
TextView statusText = (TextView) v.findViewById(R.id.status_text);
TextView principalText = (TextView) v.findViewById(R.id.principal_text);
final ImageView icon = (ImageView)v.findViewById(R.id.icon);
final ImageView presenceIcon = (ImageView)v.findViewById(R.id.presence_icon);
presenceIcon.setVisibility(View.GONE);
final ImageView nearbyIcon = (ImageView)v.findViewById(R.id.nearby_icon);
nearbyIcon.setVisibility(View.GONE);
final ImageView more = (ImageView)v.findViewById(R.id.more);
long identityId = cursor.getLong(0);
final MIdentity member = mIdentitiesManager.getIdentityForId(identityId);
if(member == null) {
unreadCount.setVisibility(View.INVISIBLE);
nameText.setText("Missing contact data...");
statusText.setText("");
icon.setImageResource(R.drawable.ic_contact_picture);
return;
}
nameText.setText(UiUtil.safeNameForIdentity(member));
principalText.setText(UiUtil.safePrincipalForIdentity(member));
icon.setImageBitmap(UiUtil.safeGetContactThumbnail(context, mIdentitiesManager, member));
//nearbyIcon.setVisibility(c.nearby ? View.VISIBLE : View.GONE);
more.setVisibility(View.GONE);
ImageView musubiEnabled = (ImageView)v.findViewById(R.id.musubi_enabled);
ImageView ignoredOverlay = (ImageView) v.findViewById(R.id.ignored_overlay);
if (member.claimed_) {
ignoredOverlay.setVisibility(View.GONE);
nameText.setTextColor(Color.BLACK);
principalText.setTextColor(Color.BLACK);
musubiEnabled.setBackgroundResource(R.drawable.musubi_enabled);
}
else {
ignoredOverlay.setVisibility(View.GONE);
nameText.setTextColor(Color.BLACK);
principalText.setTextColor(Color.BLACK);
musubiEnabled.setBackgroundResource(R.drawable.musubi_disabled);
}
if(member.blocked_) {
nameText.setTextColor(sDeletedColor);
principalText.setTextColor(sDeletedColor);
ignoredOverlay.setVisibility(View.VISIBLE);
}
}
}
public static class AddPeopleDialog extends DialogFragment {
private static final int REQUEST_ADD_CONTACT = 0;
MultiIdentitySelector mmIdentitySelector;
Activity mActivity;
public static AddPeopleDialog newInstance(Uri feedUri) {
AddPeopleDialog f = new AddPeopleDialog();
Bundle args = new Bundle();
args.putParcelable("feedUri", feedUri);
f.setArguments(args);
return f;
}
@Override
public void onAttach(SupportActivity activity) {
super.onAttach(activity);
mActivity = activity.asActivity();
mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}
@Override
public void onDetach() {
super.onDetach();
mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, R.style.Theme_D1tranlucent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_ADD_CONTACT) {
if(resultCode == Activity.RESULT_OK) {
UiUtil.addedContact(mActivity, data, mmIdentitySelector);
}
}
}
private OnRequestAddIdentityListener mOnRequestAddIdentityListener = new OnRequestAddIdentityListener() {
@Override
public void onRequestAddIdentity(String enteredText) {
Intent i = new Intent(Intent.ACTION_INSERT_OR_EDIT);
i.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
if(enteredText != null) {
Pattern emailPattern = Pattern.compile("\\b[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", Pattern.CASE_INSENSITIVE);
if (emailPattern.matcher(enteredText).matches()) {
i.putExtra(Insert.EMAIL, enteredText);
} else {
i.putExtra(Insert.NAME, enteredText);
}
}
startActivityForResult(i, REQUEST_ADD_CONTACT);
}
};
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Framework builds dialog with view defined in onCreateView.
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.getWindow().getAttributes().windowAnimations = R.style.Animation_SlideFromTopHalfHeight;
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
return dialog;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Context context = getActivity();
LinearLayout v = new LinearLayout(context);
v.setMinimumWidth(500);
v.setLayoutParams(CommonLayouts.FULL_WIDTH);
v.setOrientation(LinearLayout.VERTICAL);
v.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
RelativeLayout.LayoutParams params;
/////////////
// Action bar
/////////////
ActionBarLite actionBar = new ActionBarLite(context);
actionBar.setId(R.id.name);
actionBar.setTitle("Add People");
v.addView(actionBar);
////
// Custom action bar area
////
RelativeLayout contentFrame = new RelativeLayout(context);
contentFrame.setLayoutParams(CommonLayouts.FULL_WIDTH);
contentFrame.setBackgroundDrawable(actionBar.getBackground());
// "Add" button
Button b = new Button(context);
b.setText(R.string.add);
b.setOnClickListener(mmGoListener);
params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.BELOW, R.id.name);
b.setLayoutParams(params);
b.setId(R.id.add_contact);
contentFrame.addView(b);
// Multi-select textbox
mmIdentitySelector = new MultiIdentitySelector(context);
params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.LEFT_OF, R.id.add_contact);
params.addRule(RelativeLayout.BELOW, R.id.name);
mmIdentitySelector.setLayoutParams(params);
mmIdentitySelector.setId(R.id.people);
contentFrame.addView(mmIdentitySelector);
mmIdentitySelector.setOnRequestAddIdentityListener(mOnRequestAddIdentityListener);
int sz = 40;
sz = (int)(sz * getResources().getDisplayMetrics().density / 160);
actionBar.getCustomBar().addView(contentFrame);
return v;
}
//TODO: this code makes pretty poor use for local vars/member vars to making it clear
//and not wasteful
final View.OnClickListener mmGoListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mmIdentitySelector.getSelectedIdentities().size() == 0) {
dismiss();
return;
}
Uri feedUri = getArguments().getParcelable("feedUri");
long feedId = Long.parseLong(feedUri.getLastPathSegment());
FeedManager fm = new FeedManager(App.getDatabaseSource(mActivity));
for (MIdentity id : mmIdentitySelector.getSelectedIdentities()) {
fm.ensureFeedMember(feedId, id.id_);
}
UiUtil.addToWhitelistsIfNecessary(fm, new MyAccountManager(App.getDatabaseSource(mActivity)), fm.getFeedMembers(fm.lookupFeed(feedId)), true);
// Send an invisble feed object to force the feed to detect new members
// and recognize their names and email addresses.
Obj invitedObj = IntroductionObj.from(mmIdentitySelector.getSelectedIdentities(), true);
Helpers.sendToFeed(mActivity, invitedObj, feedUri);
App.getUsageMetrics(mActivity).report(MusubiMetrics.ADDED_PERSON_TO_FEED,
"" + mmIdentitySelector.getSelectedIdentities().size());
dismiss();
}
};
}
}