package edu.mit.mobile.android.livingpostcards;
/*
* Copyright (C) 2012-2013 MIT Mobile Experience Lab
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.io.IOException;
import android.app.Activity;
import android.content.ContentUris;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.scvngr.levelup.views.gallery.AdapterView;
import com.scvngr.levelup.views.gallery.AdapterView.OnItemSelectedListener;
import com.scvngr.levelup.views.gallery.Gallery;
import edu.mit.mobile.android.flipr.BuildConfig;
import edu.mit.mobile.android.flipr.R;
import edu.mit.mobile.android.imagecache.ImageCache;
import edu.mit.mobile.android.imagecache.ImageCache.OnImageLoadListener;
import edu.mit.mobile.android.imagecache.ImageLoaderAdapter;
import edu.mit.mobile.android.imagecache.SimpleThumbnailCursorAdapter;
import edu.mit.mobile.android.livingpostcards.DeleteDialogFragment.OnDeleteListener;
import edu.mit.mobile.android.livingpostcards.auth.Authenticator;
import edu.mit.mobile.android.livingpostcards.data.CardMedia;
import edu.mit.mobile.android.locast.data.Authorable;
public class CardMediaEditFragment extends Fragment implements LoaderCallbacks<Cursor>,
OnItemSelectedListener, OnImageLoadListener {
public static final String ARGUMENT_URI = "uri";
private static final String TAG = CardMediaEditFragment.class.getSimpleName();
private static final int NO_PENDING = -1;
private Uri mUri;
private SimpleThumbnailCursorAdapter mAdapter;
private Gallery mGallery;
private ImageView mFrame;
private ImageCache mImageCache;
private int mShowBigId;
private static final int THUMB_W = 160;
private static final int THUMB_H = 120;
private static final int HIGHRES_W = 640;
private static final int HIGHRES_H = 480;
private static final int HIGHRES_LOAD_DELAY = 100; // ms
public static CardMediaEditFragment newInstance(Uri cardMedia) {
final CardMediaEditFragment cmf = new CardMediaEditFragment();
final Bundle args = new Bundle();
args.putParcelable(ARGUMENT_URI, cardMedia);
cmf.setArguments(args);
return cmf;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCMEFHandler = new CMEFHandler(this);
mImageCache = ImageCache.getInstance(activity);
}
@Override
public void onDetach() {
super.onDetach();
mImageCache = null;
mCMEFHandler.removeMessages(CMEFHandler.MSG_LOAD_HIGHRES);
mCMEFHandler = null;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mUri = getArguments().getParcelable(ARGUMENT_URI);
if (mUri != null) {
getLoaderManager().initLoader(0, null, this);
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.card_media_edit_fragment, container, false);
mAdapter = new SimpleThumbnailCursorAdapter(getActivity(),
R.layout.card_media_thumb_square, null, new String[] { CardMedia.COL_LOCAL_URL,
CardMedia.COL_MEDIA_URL }, new int[] { R.id.card_media_thumbnail,
R.id.card_media_thumbnail }, new int[] { R.id.card_media_thumbnail }, 0);
mGallery = (Gallery) v.findViewById(R.id.gallery);
mGallery.setAdapter(new ImageLoaderAdapter(getActivity(), mAdapter, mImageCache,
new int[] { R.id.card_media_thumbnail }, THUMB_W, THUMB_H,
ImageLoaderAdapter.UNIT_PX, false));
mGallery.setWrap(true);
mGallery.setInfiniteScroll(true);
mGallery.setOnItemSelectedListener(this);
registerForContextMenu(mGallery);
mFrame = (ImageView) v.findViewById(R.id.frame);
return v;
}
@Override
public void onPause() {
super.onPause();
mGallery.pause();
mImageCache.unregisterOnImageLoadListener(this);
}
@Override
public void onResume() {
super.onResume();
mImageCache.registerOnImageLoadListener(this);
mGallery.resume();
}
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(getActivity(), mUri, new String[] { CardMedia._ID,
CardMedia.COL_LOCAL_URL, CardMedia.COL_MEDIA_URL, CardMedia.COL_AUTHOR_URI }, null,
null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
@Override
public void onItemSelected(AdapterView<?> adapter, View view, int position, long id) {
// detached
if (mCMEFHandler == null) {
return;
}
final Cursor item = (Cursor) mAdapter.getItem(position);
if (item == null) {
return;
}
String uri = item.getString(item.getColumnIndex(CardMedia.COL_LOCAL_URL));
if (uri == null) {
uri = item.getString(item.getColumnIndex(CardMedia.COL_MEDIA_URL));
}
if (uri == null) {
return;
}
if (BuildConfig.DEBUG) {
Log.d(TAG, "onItemSelected(" + adapter + ", " + view + ", " + position + ", " + id
+ ")");
}
// clear the highres request; if the load has already started, it won't be shown.
mShowHighresId = NO_PENDING;
mCMEFHandler.removeMessages(CMEFHandler.MSG_LOAD_HIGHRES);
mShowBigId = mImageCache.getNewID();
final Uri imageUri = Uri.parse(uri);
try {
final Drawable d = mImageCache.loadImage(mShowBigId, imageUri, THUMB_W, THUMB_H);
if (d != null) {
onImageLoaded(mShowBigId, imageUri, d);
}
} catch (final IOException e) {
Log.e(TAG, "Error loading image", e);
}
}
@Override
public void onNothingSelected(AdapterView<?> adapter) {
// TODO Auto-generated method stub
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
switch (v.getId()) {
case R.id.gallery: {
getActivity().getMenuInflater().inflate(R.menu.context_card_media, menu);
final Cursor c = mAdapter.getCursor();
if (c == null) {
return;
}
AdapterView.AdapterContextMenuInfo info;
try {
info = (AdapterView.AdapterContextMenuInfo) menuInfo;
} catch (final ClassCastException e) {
Log.e(TAG, "bad menuInfo", e);
return;
}
// the below is a special case due to a bug in the infinite gallery spinner.
c.moveToPosition(info.position % mAdapter.getCount());
final String myUserUri = Authenticator.getUserUri(getActivity());
final boolean isEditable = Authorable.canEdit(myUserUri, c);
menu.findItem(R.id.delete).setVisible(isEditable);
}
break;
default:
super.onCreateContextMenu(menu, v, menuInfo);
}
}
private void showDeleteDialog(Uri item) {
final DeleteDialogFragment del = DeleteDialogFragment.newInstance(item, getActivity()
.getString(R.string.delete_postcard_image),
getString(R.string.delete_postcard_image_confirm_message));
del.registerOnDeleteListener(mOnDeleteListener);
del.show(getFragmentManager(), "delete-item-dialog");
}
private final OnDeleteListener mOnDeleteListener = new OnDeleteListener() {
@Override
public void onDelete(Uri item, boolean deleted) {
}
};
private int mShowHighresId;
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info;
try {
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
} catch (final ClassCastException e) {
Log.e(TAG, "bad menuInfo", e);
return false;
}
final Uri itemUri = ContentUris.withAppendedId(mUri, info.id);
switch (item.getItemId()) {
case R.id.delete:
showDeleteDialog(itemUri);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public void onImageLoaded(int id, Uri imageUri, Drawable image) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onImageLoaded(" + id + ", " + imageUri);
}
if (id == mShowBigId || id == mShowHighresId) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "ID was either showBigId " + mShowBigId + " or highresId "
+ mShowHighresId);
}
image.setAlpha(255);
mFrame.setImageDrawable(image);
if (mShowHighresId != id) {
// the cast below is not ideal, but MAX_VALUE at ~500 loads / second would yield 50
// days worth of loads. It's unlikely that this will ever roll over unless the ID
// generator changes to something other than a simple counter.
if (!mCMEFHandler.sendMessageDelayed(mCMEFHandler.obtainMessage(
CMEFHandler.MSG_LOAD_HIGHRES, mShowBigId, 0, imageUri),
HIGHRES_LOAD_DELAY)) {
Log.e(TAG, "could not send highres load message");
}
} else {
mShowHighresId = NO_PENDING;
}
}
}
public void setAnimationTiming(int timing) {
mGallery.setInterframeDelay(timing);
}
public int getAnimationTiming() {
return (int) mGallery.getInterframeDelay();
}
public static class CMEFHandler extends Handler {
private final CardMediaEditFragment mCmef;
public static final int MSG_LOAD_HIGHRES = 100;
public CMEFHandler(CardMediaEditFragment cmef) {
mCmef = cmef;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOAD_HIGHRES:
mCmef.loadHighres(msg.arg1, (Uri) msg.obj);
break;
}
}
}
private CMEFHandler mCMEFHandler;
public void loadHighres(int loadId, Uri image) {
if (mShowBigId == loadId) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Load highres of image ID " + loadId + ": " + image);
}
mShowHighresId = mImageCache.getNewID();
mImageCache.scheduleLoadImage(mShowHighresId, image, HIGHRES_W, HIGHRES_H);
} else {
Log.w(TAG, "Did not load highres of image ID " + loadId + ": " + image
+ " as ID didn't match");
}
}
@Override
public void onImageLoaded(long id, Uri imageUri, Drawable image) {
// xxx
}
}