package kr.kdev.dg1s.biowiki.ui.media; import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Handler; import android.support.v4.widget.CursorAdapter; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewStub; import android.widget.GridView; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.NetworkImageView; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import kr.kdev.dg1s.biowiki.BioWiki; import kr.kdev.dg1s.biowiki.R; import kr.kdev.dg1s.biowiki.ui.CheckableFrameLayout; import kr.kdev.dg1s.biowiki.ui.CheckableFrameLayout.OnCheckedChangeListener; import kr.kdev.dg1s.biowiki.util.ImageHelper.BitmapWorkerCallback; import kr.kdev.dg1s.biowiki.util.ImageHelper.BitmapWorkerTask; import kr.kdev.dg1s.biowiki.util.MediaUtils; import kr.kdev.dg1s.biowiki.util.StringUtils; import kr.kdev.dg1s.biowiki.util.Utils; /** * An adapter for the media gallery listViews. */ public class MediaGridAdapter extends CursorAdapter { private final ArrayList<String> mCheckedItems; private final Map<String, List<BitmapReadyCallback>> mFilePathToCallbackMap; private final Handler mHandler; private final int mLocalImageWidth; private final LayoutInflater mInflater; private MediaGridAdapterCallback mCallback; private boolean mHasRetrievedAll; private boolean mIsRefreshing; private int mCursorDataCount; private int mGridItemWidth; private boolean mIsCurrentBlogPhotonCapable; private ImageLoader mImageLoader; public MediaGridAdapter(Context context, Cursor c, int flags, ArrayList<String> checkedItems, ImageLoader imageLoader) { super(context, c, flags); mCheckedItems = checkedItems; mLocalImageWidth = context.getResources().getDimensionPixelSize(R.dimen.media_grid_local_image_width); mInflater = LayoutInflater.from(context); mFilePathToCallbackMap = new HashMap<String, List<BitmapReadyCallback>>(); mHandler = new Handler(); setImageLoader(imageLoader); checkPhotonCapable(); } void setImageLoader(ImageLoader imageLoader) { if (imageLoader != null) { mImageLoader = imageLoader; } else { mImageLoader = BioWiki.imageLoader; } } private void checkPhotonCapable() { mIsCurrentBlogPhotonCapable = (BioWiki.getCurrentBlog() != null && BioWiki.getCurrentBlog().isPhotonCapable()); } public ArrayList<String> getCheckedItems() { return mCheckedItems; } @SuppressLint("DefaultLocale") @Override public void bindView(final View view, Context context, Cursor cursor) { int itemViewType = getItemViewType(cursor.getPosition()); if (itemViewType == ViewTypes.PROGRESS.ordinal()) { if (mIsRefreshing) { int height = mContext.getResources().getDimensionPixelSize(R.dimen.media_grid_progress_height); view.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.MATCH_PARENT, height)); view.setVisibility(View.VISIBLE); } else { view.setLayoutParams(new GridView.LayoutParams(0, 0)); view.setVisibility(View.GONE); } return; } else if (itemViewType == ViewTypes.SPACER.ordinal()) { CheckableFrameLayout frameLayout = (CheckableFrameLayout) view.findViewById(R.id.media_grid_frame_layout); updateGridWidth(context, frameLayout); view.setVisibility(View.INVISIBLE); return; } final GridViewHolder holder; if (view.getTag() instanceof GridViewHolder) { holder = (GridViewHolder) view.getTag(); } else { holder = new GridViewHolder(view); view.setTag(holder); } final String mediaId = cursor.getString(cursor.getColumnIndex("mediaId")); String state = cursor.getString(cursor.getColumnIndex("uploadState")); boolean isLocalFile = MediaUtils.isLocalFile(state); // file name String fileName = cursor.getString(cursor.getColumnIndex("fileName")); if (holder.filenameView != null) { holder.filenameView.setText(fileName); } // title of media String title = cursor.getString(cursor.getColumnIndex("title")); if (title == null || title.equals("")) title = fileName; holder.titleView.setText(title); // upload date if (holder.uploadDateView != null) { String date = MediaUtils.getDate(cursor.getLong(cursor.getColumnIndex("date_created_gmt"))); holder.uploadDateView.setText(date); } // load image if (isLocalFile) { loadLocalImage(cursor, holder.imageView); } else { loadNetworkImage(cursor, (NetworkImageView) holder.imageView); } // get the file extension from the fileURL String mimeType = cursor.getString(cursor.getColumnIndex("mimeType")); String fileExtension = MediaUtils.getExtensionForMimeType(mimeType); fileExtension = fileExtension.toUpperCase(); // file type if (Utils.isXLarge(context) && !TextUtils.isEmpty(fileExtension)) { holder.fileTypeView.setText("File type: " + fileExtension); } else { holder.fileTypeView.setText(fileExtension); } // dimensions String filePath = cursor.getString(cursor.getColumnIndex("fileURL")); TextView dimensionView = (TextView) view.findViewById(R.id.media_grid_item_dimension); if (dimensionView != null) { if (MediaUtils.isValidImage(filePath)) { int width = cursor.getInt(cursor.getColumnIndex("width")); int height = cursor.getInt(cursor.getColumnIndex("height")); if (width > 0 && height > 0) { String dimensions = width + "x" + height; holder.dimensionView.setText(dimensions); holder.dimensionView.setVisibility(View.VISIBLE); } } else { holder.dimensionView.setVisibility(View.GONE); } } // multi-select highlighting holder.frameLayout.setTag(mediaId); holder.frameLayout.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CheckableFrameLayout view, boolean isChecked) { String mediaId = (String) view.getTag(); if (isChecked) { if (!mCheckedItems.contains(mediaId)) { mCheckedItems.add(mediaId); } } else { mCheckedItems.remove(mediaId); } } }); holder.frameLayout.setChecked(mCheckedItems.contains(mediaId)); // resizing layout to fit nicely into grid view updateGridWidth(context, holder.frameLayout); // show upload state if (holder.stateTextView != null) { if (state != null && state.length() > 0) { // show the progressbar only when the state is uploading if (state.equals("uploading")) { holder.progressUpload.setVisibility(View.VISIBLE); } else { holder.progressUpload.setVisibility(View.GONE); } // add onclick to retry failed uploads if (state.equals("failed")) { state = "retry"; holder.stateTextView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!inMultiSelect()) { ((TextView) v).setText("queued"); v.setOnClickListener(null); mCallback.onRetryUpload(mediaId); } } }); } holder.stateTextView.setText(state); holder.uploadStateView.setVisibility(View.VISIBLE); } else { holder.uploadStateView.setVisibility(View.GONE); } } // if we are near the end, make a call to fetch more int position = cursor.getPosition(); if (position == mCursorDataCount - 1 && !mHasRetrievedAll) { if (mCallback != null) { mCallback.fetchMoreData(mCursorDataCount); } } } private boolean inMultiSelect() { return mCallback.isInMultiSelect(); } private void loadNetworkImage(Cursor cursor, NetworkImageView imageView) { String thumbnailURL = cursor.getString(cursor.getColumnIndex("thumbnailURL")); // Allow non-private wp.com and Jetpack blogs to use photon to get a higher res thumbnail if (mIsCurrentBlogPhotonCapable) { String imageURL = cursor.getString(cursor.getColumnIndex("fileURL")); if (imageURL != null) { thumbnailURL = StringUtils.getPhotonUrl(imageURL, mGridItemWidth); } } if (thumbnailURL != null) { Uri uri = Uri.parse(thumbnailURL); String filepath = uri.getLastPathSegment(); int placeholderResId = MediaUtils.getPlaceholder(filepath); imageView.setImageResource(0); imageView.setErrorImageResId(placeholderResId); // no default image while downloading imageView.setDefaultImageResId(0); if (MediaUtils.isValidImage(filepath)) { imageView.setTag(thumbnailURL); imageView.setImageUrl(thumbnailURL, mImageLoader); } else { imageView.setImageResource(placeholderResId); } } else { imageView.setImageResource(0); } } private synchronized void loadLocalImage(Cursor cursor, final ImageView imageView) { final String filePath = cursor.getString(cursor.getColumnIndex("filePath")); if (MediaUtils.isValidImage(filePath)) { imageView.setTag(filePath); Bitmap bitmap = BioWiki.getBitmapCache().get(filePath); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageBitmap(null); boolean shouldFetch = false; List<BitmapReadyCallback> list; if (mFilePathToCallbackMap.containsKey(filePath)) { list = mFilePathToCallbackMap.get(filePath); } else { list = new ArrayList<MediaGridAdapter.BitmapReadyCallback>(); shouldFetch = true; mFilePathToCallbackMap.put(filePath, list); } list.add(new BitmapReadyCallback() { @Override public void onBitmapReady(Bitmap bitmap) { if (imageView.getTag() instanceof String && imageView.getTag().equals(filePath)) imageView.setImageBitmap(bitmap); } }); if (shouldFetch) { fetchBitmap(filePath); } } } else { // if not image, for now show no image. imageView.setImageBitmap(null); } } private void fetchBitmap(final String filePath) { BitmapWorkerTask task = new BitmapWorkerTask(null, mLocalImageWidth, mLocalImageWidth, new BitmapWorkerCallback() { @Override public void onBitmapReady(final String path, ImageView imageView, final Bitmap bitmap) { mHandler.post(new Runnable() { @Override public void run() { List<BitmapReadyCallback> callbacks = mFilePathToCallbackMap.get(path); for (BitmapReadyCallback callback : callbacks) { callback.onBitmapReady(bitmap); } BioWiki.getBitmapCache().put(path, bitmap); callbacks.clear(); mFilePathToCallbackMap.remove(path); } }); } }); task.execute(filePath); } @Override public View newView(Context context, Cursor cursor, ViewGroup root) { int itemViewType = getItemViewType(cursor.getPosition()); // spacer and progress spinner views if (itemViewType == ViewTypes.PROGRESS.ordinal()) { return mInflater.inflate(R.layout.media_grid_progress, root, false); } else if (itemViewType == ViewTypes.SPACER.ordinal()) { return mInflater.inflate(R.layout.media_grid_item, root, false); } View view = mInflater.inflate(R.layout.media_grid_item, root, false); ViewStub imageStub = (ViewStub) view.findViewById(R.id.media_grid_image_stub); // We need to use ViewStubs to inflate the image to either: // - a regular ImageView (for local images) // - a FadeInNetworkImageView (for network images) // This is because the NetworkImageView can't load local images. // The other option would be to inflate multiple layouts, but that would lead // to extra near-duplicate xml files that would need to be maintained. if (itemViewType == ViewTypes.LOCAL.ordinal()) { imageStub.setLayoutResource(R.layout.media_grid_image_local); } else { imageStub.setLayoutResource(R.layout.media_grid_image_network); } imageStub.inflate(); view.setTag(new GridViewHolder(view)); return view; } @Override public int getViewTypeCount() { return ViewTypes.values().length; } @Override public int getItemViewType(int position) { Cursor cursor = getCursor(); cursor.moveToPosition(position); // spacer / progress cells int _id = cursor.getInt(cursor.getColumnIndex("_id")); if (_id < 0) { if (_id == Integer.MIN_VALUE) return ViewTypes.PROGRESS.ordinal(); else return ViewTypes.SPACER.ordinal(); } // regular cells String state = cursor.getString(cursor.getColumnIndex("uploadState")); if (MediaUtils.isLocalFile(state)) return ViewTypes.LOCAL.ordinal(); else return ViewTypes.NETWORK.ordinal(); } /** * Updates the width of a cell to max out the space available, for phones * */ private void updateGridWidth(Context context, View view) { setGridItemWidth(); int columnCount = getColumnCount(context); if (columnCount > 1) { RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(mGridItemWidth, mGridItemWidth); int margins = (int) Utils.dpToPx(8); params.setMargins(0, margins, 0, margins); params.addRule(RelativeLayout.CENTER_IN_PARENT, 1); view.setLayoutParams(params); } } @Override public Cursor swapCursor(Cursor newCursor) { checkPhotonCapable(); if (newCursor == null) { mCursorDataCount = 0; return super.swapCursor(newCursor); } mCursorDataCount = newCursor.getCount(); // to mimic the infinite the notification's infinite scroll ui // (with a progress spinner on the bottom of the list), we'll need to add // extra cells in the gridview: // - spacer cells as fillers to place the progress spinner on the first cell (_id < 0) // - progress spinner cell (_id = Integer.MIN_VALUE) // use a matrix cursor to create the extra rows MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_id"}); // add spacer cells int columnCount = getColumnCount(mContext); int remainder = newCursor.getCount() % columnCount; if (remainder > 0) { int spaceCount = columnCount - remainder; for (int i = 0; i < spaceCount; i++) { int id = i - spaceCount; matrixCursor.addRow(new Object[]{id + ""}); } } // add progress spinner cell matrixCursor.addRow(new Object[]{Integer.MIN_VALUE}); // use a merge cursor to place merge the extra rows at the bottom of the newly swapped cursor MergeCursor mergeCursor = new MergeCursor(new Cursor[]{newCursor, matrixCursor}); return super.swapCursor(mergeCursor); } /** * Return the number of columns in the media grid * */ private int getColumnCount(Context context) { return context.getResources().getInteger(R.integer.media_grid_num_columns); } public void setCallback(MediaGridAdapterCallback callback) { mCallback = callback; } public void setHasRetrievedAll(boolean b) { mHasRetrievedAll = b; } public void setRefreshing(boolean refreshing) { mIsRefreshing = refreshing; notifyDataSetChanged(); } public int getDataCount() { return mCursorDataCount; } private void setGridItemWidth() { int maxWidth = mContext.getResources().getDisplayMetrics().widthPixels; int columnCount = getColumnCount(mContext); if (columnCount > 0) { int dp8 = (int) Utils.dpToPx(8); int padding = (columnCount + 1) * dp8; mGridItemWidth = (maxWidth - padding) / columnCount; } } private static enum ViewTypes { LOCAL, NETWORK, PROGRESS, SPACER } public interface MediaGridAdapterCallback { public void fetchMoreData(int offset); public void onRetryUpload(String mediaId); public boolean isInMultiSelect(); } interface BitmapReadyCallback { void onBitmapReady(Bitmap bitmap); } private static class GridViewHolder { private final TextView filenameView; private final TextView titleView; private final TextView uploadDateView; private final ImageView imageView; private final TextView fileTypeView; private final TextView dimensionView; private final CheckableFrameLayout frameLayout; private final TextView stateTextView; private final ProgressBar progressUpload; private final RelativeLayout uploadStateView; GridViewHolder(View view) { filenameView = (TextView) view.findViewById(R.id.media_grid_item_filename); titleView = (TextView) view.findViewById(R.id.media_grid_item_name); uploadDateView = (TextView) view.findViewById(R.id.media_grid_item_upload_date); imageView = (ImageView) view.findViewById(R.id.media_grid_item_image); fileTypeView = (TextView) view.findViewById(R.id.media_grid_item_filetype); dimensionView = (TextView) view.findViewById(R.id.media_grid_item_dimension); frameLayout = (CheckableFrameLayout) view.findViewById(R.id.media_grid_frame_layout); stateTextView = (TextView) view.findViewById(R.id.media_grid_item_upload_state); progressUpload = (ProgressBar) view.findViewById(R.id.media_grid_item_upload_progress); uploadStateView = (RelativeLayout) view.findViewById(R.id.media_grid_item_upload_state_container); } } }