package org.wordpress.android.ui.photopicker;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.wordpress.android.R;
import org.wordpress.android.ui.media.MediaPreviewActivity;
import org.wordpress.android.util.AniUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.DisplayUtils;
import org.wordpress.android.util.SqlUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
import static org.wordpress.android.ui.photopicker.PhotoPickerFragment.NUM_COLUMNS;
class PhotoPickerAdapter extends RecyclerView.Adapter<PhotoPickerAdapter.ThumbnailViewHolder> {
private static final float SCALE_NORMAL = 1.0f;
private static final float SCALE_SELECTED = .85f;
/*
* used by this adapter to communicate with the owning fragment
*/
protected interface PhotoPickerAdapterListener {
void onItemTapped(Uri mediaUri);
void onSelectedCountChanged(int count);
void onAdapterLoaded(boolean isEmpty);
}
private class PhotoPickerItem {
private long _id;
private Uri uri;
private boolean isVideo;
}
class UriList extends ArrayList<Uri> {
private int indexOfUri(Uri imageUri) {
for (int i = 0; i < size(); i++) {
if (get(i).equals(imageUri)) {
return i;
}
}
return -1;
}
}
private final UriList mSelectedUris = new UriList();
private final Context mContext;
private int mThumbWidth;
private int mThumbHeight;
private boolean mAllowMultiSelect;
private boolean mIsMultiSelectEnabled;
private boolean mPhotosOnly;
private final ThumbnailLoader mThumbnailLoader;
private final PhotoPickerAdapterListener mListener;
private final LayoutInflater mInflater;
private final ArrayList<PhotoPickerItem> mMediaList = new ArrayList<>();
public PhotoPickerAdapter(Context context,
PhotoPickerAdapterListener listener) {
super();
mContext = context;
mListener = listener;
mInflater = LayoutInflater.from(context);
mThumbnailLoader = new ThumbnailLoader(context);
setHasStableIds(true);
}
void refresh(boolean forceReload) {
int displayWidth = DisplayUtils.getDisplayPixelWidth(mContext);
int thumbWidth = displayWidth / NUM_COLUMNS;
int thumbHeight = (int) (thumbWidth * 0.75f);
boolean sizeChanged = thumbWidth != mThumbWidth || thumbHeight != mThumbHeight;
// if thumb sizes have changed (due to device rotation, or never being set), we must
// reload from scratch - otherwise we can do a refresh so the adapter is only loaded
// if there are changes
boolean mustReload;
if (sizeChanged) {
mThumbWidth = thumbWidth;
mThumbHeight = thumbHeight;
mustReload = true;
} else {
mustReload = forceReload;
}
new BuildDeviceMediaListTask(mustReload).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public int getItemCount() {
return mMediaList.size();
}
@Override
public long getItemId(int position) {
if (isValidPosition(position)) {
return getItemAtPosition(position)._id;
} else {
return NO_POSITION;
}
}
private boolean isEmpty() {
return mMediaList.size() == 0;
}
@Override
public ThumbnailViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.photo_picker_thumbnail, parent, false);
return new ThumbnailViewHolder(view);
}
@Override
public void onBindViewHolder(ThumbnailViewHolder holder, int position) {
PhotoPickerItem item = getItemAtPosition(position);
if (item == null) {
return;
}
int selectedIndex = mIsMultiSelectEnabled ? mSelectedUris.indexOfUri(item.uri) : -1;
if (selectedIndex > -1) {
holder.txtSelectionCount.setVisibility(View.VISIBLE);
holder.txtSelectionCount.setText(Integer.toString(selectedIndex + 1));
} else {
holder.txtSelectionCount.setVisibility(View.GONE);
}
// make sure the thumbnail scale reflects its selection state
float scale = selectedIndex > -1 ? SCALE_SELECTED : SCALE_NORMAL;
if (holder.imgThumbnail.getScaleX() != scale) {
holder.imgThumbnail.setScaleX(scale);
holder.imgThumbnail.setScaleY(scale);
}
holder.videoOverlay.setVisibility(item.isVideo ? View.VISIBLE : View.GONE);
mThumbnailLoader.loadThumbnail(holder.imgThumbnail, item._id, item.isVideo);
}
private PhotoPickerItem getItemAtPosition(int position) {
if (!isValidPosition(position)) {
AppLog.w(AppLog.T.POSTS, "photo picker > invalid position in getItemAtPosition");
return null;
}
return mMediaList.get(position);
}
private boolean isValidPosition(int position) {
return position >= 0 && position < mMediaList.size();
}
void setAllowMultiSelect(boolean allow) {
mAllowMultiSelect = allow;
}
void setShowPhotosOnly(boolean value) {
mPhotosOnly = value;
}
void setMultiSelectEnabled(boolean enabled) {
if (mIsMultiSelectEnabled == enabled) return;
mIsMultiSelectEnabled = enabled;
if (!enabled && mSelectedUris.size() > 0) {
mSelectedUris.clear();
notifyDataSetChangedNoFade();
}
}
/*
* toggles the selection state of the item at the passed position
*/
private void toggleSelection(ThumbnailViewHolder holder, int position) {
PhotoPickerItem item = getItemAtPosition(position);
if (item == null) {
return;
}
boolean isSelected;
int selectedIndex = mSelectedUris.indexOfUri(item.uri);
if (selectedIndex > -1) {
mSelectedUris.remove(selectedIndex);
isSelected = false;
} else {
mSelectedUris.add(item.uri);
isSelected = true;
holder.txtSelectionCount.setText(Integer.toString(mSelectedUris.size()));
}
// animate the count
AniUtils.startAnimation(holder.txtSelectionCount,
isSelected ? R.anim.cab_select : R.anim.cab_deselect);
holder.txtSelectionCount.setVisibility(isSelected ? View.VISIBLE : View.GONE);
// scale the thumbnail
if (isSelected) {
AniUtils.scale(holder.imgThumbnail, SCALE_NORMAL, SCALE_SELECTED, AniUtils.Duration.SHORT);
} else {
AniUtils.scale(holder.imgThumbnail, SCALE_SELECTED, SCALE_NORMAL, AniUtils.Duration.SHORT);
}
if (mListener != null) {
mListener.onSelectedCountChanged(getNumSelected());
}
// redraw the grid after the scale animation completes
long delayMs = AniUtils.Duration.SHORT.toMillis(mContext);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
notifyDataSetChangedNoFade();
}
}, delayMs);
}
@NonNull
ArrayList<Uri> getSelectedURIs() {
//noinspection unchecked
return (ArrayList<Uri>)mSelectedUris.clone();
}
int getNumSelected() {
return mIsMultiSelectEnabled ? mSelectedUris.size() : 0;
}
/*
* calls notifyDataSetChanged() with the ThumbnailLoader image fade disabled - used to
* prevent unnecessary flicker when changing existing items
*/
private void notifyDataSetChangedNoFade() {
mThumbnailLoader.temporarilyDisableFade();
notifyDataSetChanged();
}
/*
* ViewHolder containing a device thumbnail
*/
class ThumbnailViewHolder extends RecyclerView.ViewHolder {
private final ImageView imgThumbnail;
private final TextView txtSelectionCount;
private final View videoOverlay;
public ThumbnailViewHolder(View view) {
super(view);
imgThumbnail = (ImageView) view.findViewById(R.id.image_thumbnail);
txtSelectionCount = (TextView) view.findViewById(R.id.text_selection_count);
videoOverlay = view.findViewById(R.id.image_video_overlay);
imgThumbnail.getLayoutParams().width = mThumbWidth;
imgThumbnail.getLayoutParams().height = mThumbHeight;
imgThumbnail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (isValidPosition(position)) {
if (mIsMultiSelectEnabled) {
toggleSelection(ThumbnailViewHolder.this, position);
} else if (mListener != null) {
Uri uri = getItemAtPosition(position).uri;
mListener.onItemTapped(uri);
}
}
}
});
imgThumbnail.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = getAdapterPosition();
if (isValidPosition(position) && mAllowMultiSelect) {
if (!mIsMultiSelectEnabled) {
setMultiSelectEnabled(true);
}
toggleSelection(ThumbnailViewHolder.this, position);
}
return true;
}
});
View imgPreview = view.findViewById(R.id.image_preview);
imgPreview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
PhotoPickerItem item = getItemAtPosition(position);
if (item != null) {
MediaPreviewActivity.showPreview(
mContext,
imgThumbnail,
item.uri.toString(),
item.isVideo);
}
}
});
}
}
/*
* builds the list of media items from the device
*/
private class BuildDeviceMediaListTask extends AsyncTask<Void, Void, Boolean> {
private final ArrayList<PhotoPickerItem> tmpList = new ArrayList<>();
private final boolean reload;
private static final String ID_COL = MediaStore.Images.Media._ID;
BuildDeviceMediaListTask(boolean mustReload) {
super();
reload = mustReload;
}
@Override
protected Boolean doInBackground(Void... params) {
// images
addMedia(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false);
// videos
if (!mPhotosOnly) {
addMedia(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true);
}
// sort by id in reverse (newest first)
Collections.sort(tmpList, new Comparator<PhotoPickerItem>() {
@Override
public int compare(PhotoPickerItem o1, PhotoPickerItem o2) {
long id1 = o1._id;
long id2 = o2._id;
return (id2 < id1) ? -1 : ((id1 == id2) ? 0 : 1);
}
});
// if we're reloading then return true so the adapter is updated, otherwise only
// return true if changes are detected
return reload || !isSameMediaList();
}
private void addMedia(Uri baseUri, boolean isVideo) {
String[] projection = { ID_COL };
Cursor cursor = mContext.getContentResolver().query(
baseUri,
projection,
null,
null,
null);
if (cursor == null) {
return;
}
try {
int idIndex = cursor.getColumnIndexOrThrow(ID_COL);
while (cursor.moveToNext()) {
PhotoPickerItem item = new PhotoPickerItem();
item._id = cursor.getLong(idIndex);
item.uri = Uri.withAppendedPath(baseUri, "" + item._id);
item.isVideo = isVideo;
tmpList.add(item);
}
} finally {
SqlUtils.closeCursor(cursor);
}
}
// returns true if the media list built here is the same as the existing one
private boolean isSameMediaList() {
if (tmpList.size() != mMediaList.size()) {
return false;
}
for (int i = 0; i < tmpList.size(); i++) {
if (tmpList.get(i)._id != mMediaList.get(i)._id) {
return false;
}
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
mMediaList.clear();
mMediaList.addAll(tmpList);
notifyDataSetChanged();
}
if (mListener != null) {
mListener.onAdapterLoaded(isEmpty());
}
}
}
}