package org.wordpress.android.ui.media; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Fragment; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.ActionMode; import android.support.v7.widget.AppCompatSpinner; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.TextView; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.fluxc.Dispatcher; import org.wordpress.android.fluxc.generated.MediaActionBuilder; import org.wordpress.android.fluxc.model.MediaModel; import org.wordpress.android.fluxc.model.SiteModel; import org.wordpress.android.fluxc.store.MediaStore; import org.wordpress.android.fluxc.store.MediaStore.FetchMediaListPayload; import org.wordpress.android.fluxc.store.MediaStore.MediaErrorType; import org.wordpress.android.fluxc.store.MediaStore.OnMediaListFetched; import org.wordpress.android.fluxc.tools.FluxCImageLoader; import org.wordpress.android.models.MediaUploadState; import org.wordpress.android.ui.EmptyViewMessageType; import org.wordpress.android.ui.media.MediaGridAdapter.MediaGridAdapterCallback; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.ListUtils; import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.ToastUtils.Duration; import org.wordpress.android.util.WPActivityUtils; import org.wordpress.android.util.helpers.SwipeToRefreshHelper; import org.wordpress.android.util.helpers.SwipeToRefreshHelper.RefreshListener; import org.wordpress.android.util.widgets.CustomSwipeRefreshLayout; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; /** * The grid displaying the media items. */ @SuppressWarnings("ALL") public class MediaGridFragment extends Fragment implements MediaGridAdapterCallback { private static final String BUNDLE_SELECTED_STATES = "BUNDLE_SELECTED_STATES"; private static final String BUNDLE_IN_MULTI_SELECT_MODE = "BUNDLE_IN_MULTI_SELECT_MODE"; private static final String BUNDLE_SCROLL_POSITION = "BUNDLE_SCROLL_POSITION"; private static final String BUNDLE_HAS_RETRIEVED_ALL_MEDIA = "BUNDLE_HAS_RETRIEVED_ALL_MEDIA"; private static final String BUNDLE_FILTER = "BUNDLE_FILTER"; private static final String BUNDLE_EMPTY_VIEW_MESSAGE = "BUNDLE_EMPTY_VIEW_MESSAGE"; @Inject Dispatcher mDispatcher; @Inject MediaStore mMediaStore; @Inject FluxCImageLoader mImageLoader; private Filter mFilter = Filter.ALL; private String[] mFiltersText; private RecyclerView mRecycler; private GridLayoutManager mGridManager; private MediaGridAdapter mGridAdapter; private MediaGridListener mListener; private boolean mIsRefreshing; private boolean mHasRetrievedAllMedia; private ActionMode mActionMode; private String mSearchTerm; private TextView mResultView; private AppCompatSpinner mSpinner; private SwipeToRefreshHelper mSwipeToRefreshHelper; private LinearLayout mEmptyView; private TextView mEmptyViewTitle; private EmptyViewMessageType mEmptyViewMessageType = EmptyViewMessageType.NO_CONTENT; private boolean mSpinnerHasLaunched; private SiteModel mSite; public interface MediaGridListener { void onMediaItemSelected(View sourceView, int localMediaId); void onRetryUpload(int localMediaId); } public enum Filter { ALL, IMAGES, UNATTACHED; public static Filter getFilter(int filterPos) { if (filterPos > Filter.values().length) return ALL; else return Filter.values()[filterPos]; } } private final OnItemSelectedListener mFilterSelectedListener = new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { // onItemSelected will be called during initialization, so ignore first call if (!mSpinnerHasLaunched) { mSpinnerHasLaunched = true; return; } setFilter(Filter.getFilter(position)); } @Override public void onNothingSelected(AdapterView<?> parent) { } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((WordPress) getActivity().getApplication()).component().inject(this); if (savedInstanceState == null) { if (getArguments() != null) { mSite = (SiteModel) getArguments().getSerializable(WordPress.SITE); } else { mSite = (SiteModel) getActivity().getIntent().getSerializableExtra(WordPress.SITE); } } else { mSite = (SiteModel) savedInstanceState.getSerializable(WordPress.SITE); } if (mSite == null) { ToastUtils.showToast(getActivity(), R.string.blog_not_found, ToastUtils.Duration.SHORT); getActivity().finish(); } } @Override public void onStart() { super.onStart(); mDispatcher.register(this); } @Override public void onResume() { super.onResume(); refreshSpinnerAdapter(); refreshMediaFromDB(); } @Override public void onStop() { mDispatcher.unregister(this); super.onStop(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); saveState(outState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); mFiltersText = new String[Filter.values().length]; View view = inflater.inflate(R.layout.media_grid_fragment, container); mRecycler = (RecyclerView) view.findViewById(R.id.recycler); mRecycler.setHasFixedSize(true); int numColumns = MediaGridAdapter.getColumnCount(getActivity()); mGridManager = new GridLayoutManager(getActivity(), numColumns); mRecycler.setLayoutManager(mGridManager); mGridAdapter = new MediaGridAdapter(getActivity(), mSite, mImageLoader); mGridAdapter.setCallback(this); mGridAdapter.setAllowMultiselect(true); mRecycler.setAdapter(mGridAdapter); mEmptyView = (LinearLayout) view.findViewById(R.id.empty_view); mEmptyViewTitle = (TextView) view.findViewById(R.id.empty_view_title); mResultView = (TextView) view.findViewById(R.id.media_filter_result_text); mSpinner = (AppCompatSpinner) view.findViewById(R.id.media_filter_spinner); mSpinner.setOnItemSelectedListener(mFilterSelectedListener); // swipe to refresh setup mSwipeToRefreshHelper = new SwipeToRefreshHelper(getActivity(), (CustomSwipeRefreshLayout) view.findViewById(R.id.ptr_layout), new RefreshListener() { @Override public void onRefreshStarted() { if (!isAdded()) { return; } if (!NetworkUtils.checkConnection(getActivity())) { updateEmptyView(EmptyViewMessageType.NETWORK_ERROR); mSwipeToRefreshHelper.setRefreshing(false); return; } fetchMediaList(false); } }); restoreState(savedInstanceState); setupSpinnerAdapter(); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (MediaGridListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement MediaGridListener"); } } @Override public void onAdapterFetchMoreData() { if (!mHasRetrievedAllMedia) { fetchMediaList(true); } } @Override public void onAdapterRetryUpload(int localMediaId) { mListener.onRetryUpload(localMediaId); } @Override public void onAdapterItemSelected(View sourceView, int position) { int localMediaId = mGridAdapter.getLocalMediaIdAtPosition(position); mListener.onMediaItemSelected(sourceView, localMediaId); } @Override public void onAdapterSelectionCountChanged(int count) { if (count == 0 && mActionMode != null) { mActionMode.finish(); } else if (mActionMode == null) { ((AppCompatActivity) getActivity()).startSupportActionMode(new ActionModeCallback()); } setFilterEnabled(count == 0); updateActionModeTitle(count); } @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onMediaListFetched(OnMediaListFetched event) { if (event.isError()) { handleFetchAllMediaError(event.error.type); return; } handleFetchAllMediaSuccess(event); } public void refreshSpinnerAdapter() { updateFilterText(); updateSpinnerAdapter(); } public void refreshMediaFromDB() { if (!isAdded()) return; setFilter(mFilter); updateFilterText(); updateSpinnerAdapter(); if (mGridAdapter.getItemCount() == 0) { if (NetworkUtils.isNetworkAvailable(getActivity())) { if (!mHasRetrievedAllMedia) { fetchMediaList(false); } } else { updateEmptyView(EmptyViewMessageType.NETWORK_ERROR); } } } public void search(String searchTerm) { mSearchTerm = searchTerm; List<MediaModel> mediaList = mMediaStore.searchSiteMediaByTitle(mSite, mSearchTerm); mGridAdapter.setMediaList(mediaList); } public void setFilterEnabled(boolean enabled) { if (mSpinner != null) { mSpinner.setEnabled(enabled); } } public void setFilter(Filter filter) { mFilter = filter; List<MediaModel> mediaList = filterItems(mFilter); mGridAdapter.setMediaList(mediaList); if (mediaList.size() == 0) { mResultView.setVisibility(View.GONE); } else { hideEmptyView(); } // Overwrite the LOADING message if (mEmptyViewMessageType == EmptyViewMessageType.LOADING) { updateEmptyView(EmptyViewMessageType.NO_CONTENT); } else { updateEmptyView(mEmptyViewMessageType); } } public void clearSelectedItems() { mGridAdapter.clearSelection(); } public void removeFromMultiSelect(int localMediaId) { if (mGridAdapter.isInMultiSelect() && mGridAdapter.isItemSelected(localMediaId)) { mGridAdapter.removeSelectionByLocalId(localMediaId); setFilterEnabled(mGridAdapter.getSelectedItems().size() == 0); } } private void setRefreshing(boolean refreshing) { mSwipeToRefreshHelper.setRefreshing(refreshing); } private void setSwipeToRefreshEnabled(boolean enabled) { mSwipeToRefreshHelper.setEnabled(enabled); } public void updateSpinnerAdapter() { ArrayAdapter<String> adapter = (ArrayAdapter<String>) mSpinner.getAdapter(); if (adapter != null) { adapter.notifyDataSetChanged(); } } public void updateFilterText() { int countAll = mMediaStore.getAllSiteMedia(mSite).size(); int countImages = mMediaStore.getSiteImages(mSite).size(); int countUnattached = mMediaStore.getUnattachedSiteMedia(mSite).size(); mFiltersText[0] = getResources().getString(R.string.all) + " (" + countAll + ")"; mFiltersText[1] = getResources().getString(R.string.images) + " (" + countImages + ")"; mFiltersText[2] = getResources().getString(R.string.unattached) + " (" + countUnattached + ")"; } private List<MediaModel> filterItems(Filter filter) { switch (filter) { case IMAGES: return mMediaStore.getSiteImages(mSite); case UNATTACHED: return mMediaStore.getUnattachedSiteMedia(mSite); default: return mMediaStore.getAllSiteMedia(mSite); } } private void updateEmptyView(EmptyViewMessageType emptyViewMessageType) { if (mEmptyView != null) { if (mGridAdapter.getItemCount() == 0) { int stringId = 0; switch (emptyViewMessageType) { case LOADING: stringId = R.string.media_fetching; break; case NO_CONTENT: stringId = R.string.media_empty_list; break; case NETWORK_ERROR: stringId = R.string.no_network_message; break; case PERMISSION_ERROR: stringId = R.string.media_error_no_permission; break; case GENERIC_ERROR: stringId = R.string.error_refresh_media; break; } mEmptyViewTitle.setText(getText(stringId)); mEmptyViewMessageType = emptyViewMessageType; mEmptyView.setVisibility(View.VISIBLE); } else { mEmptyView.setVisibility(View.GONE); } } } private void hideEmptyView() { if (mEmptyView != null) { mEmptyView.setVisibility(View.GONE); } } private void saveState(Bundle outState) { outState.putIntArray(BUNDLE_SELECTED_STATES, ListUtils.toIntArray(mGridAdapter.getSelectedItems())); outState.putInt(BUNDLE_SCROLL_POSITION, mGridManager.findFirstCompletelyVisibleItemPosition()); outState.putBoolean(BUNDLE_HAS_RETRIEVED_ALL_MEDIA, mHasRetrievedAllMedia); outState.putBoolean(BUNDLE_IN_MULTI_SELECT_MODE, mGridAdapter.isInMultiSelect()); outState.putInt(BUNDLE_FILTER, mFilter.ordinal()); outState.putString(BUNDLE_EMPTY_VIEW_MESSAGE, mEmptyViewMessageType.name()); outState.putSerializable(WordPress.SITE, mSite); } private void setupSpinnerAdapter() { if (getActivity() == null) { return; } updateFilterText(); Context context = WPActivityUtils.getThemedContext(getActivity()); ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.spinner_menu_dropdown_item, mFiltersText); mSpinner.setAdapter(adapter); mSpinner.setSelection(mFilter.ordinal()); } private void updateActionModeTitle(int selectCount) { if (mActionMode != null) { mActionMode.setTitle(String.format(getString(R.string.cab_selected), selectCount)); } } private void handleMultiSelectDelete() { if (!isAdded()) { return; } Builder builder = new AlertDialog.Builder(getActivity()).setMessage(R.string.confirm_delete_multi_media) .setCancelable(true).setPositiveButton( R.string.delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (getActivity() instanceof MediaBrowserActivity) { ((MediaBrowserActivity) getActivity()).deleteMedia( mGridAdapter.getSelectedItems()); } // update upload state for (int itemId : mGridAdapter.getSelectedItems()) { MediaModel media = mMediaStore.getMediaWithLocalId(itemId); media.setUploadState(MediaUploadState.DELETING.name()); mDispatcher.dispatch(MediaActionBuilder.newUpdateMediaAction(media)); } mGridAdapter.clearSelection(); if (mActionMode != null) { mActionMode.finish(); } } }).setNegativeButton(R.string.cancel, null); AlertDialog dialog = builder.create(); dialog.show(); } private void restoreState(Bundle savedInstanceState) { if (savedInstanceState == null) return; boolean isInMultiSelectMode = savedInstanceState.getBoolean(BUNDLE_IN_MULTI_SELECT_MODE); if (isInMultiSelectMode) { mGridAdapter.setInMultiSelect(true); if (savedInstanceState.containsKey(BUNDLE_SELECTED_STATES)) { ArrayList<Integer> selectedItems = ListUtils.fromIntArray(savedInstanceState.getIntArray(BUNDLE_SELECTED_STATES)); mGridAdapter.setSelectedItems(selectedItems); setFilterEnabled(mGridAdapter.getSelectedItems().size() == 0); mSwipeToRefreshHelper.setEnabled(false); } } mHasRetrievedAllMedia = savedInstanceState.getBoolean(BUNDLE_HAS_RETRIEVED_ALL_MEDIA, false); mFilter = Filter.getFilter(savedInstanceState.getInt(BUNDLE_FILTER)); mEmptyViewMessageType = EmptyViewMessageType.getEnumFromString(savedInstanceState. getString(BUNDLE_EMPTY_VIEW_MESSAGE)); } private void fetchMediaList(boolean loadMore) { // do not refresh if there is no network if (!NetworkUtils.isNetworkAvailable(getActivity())) { updateEmptyView(EmptyViewMessageType.NETWORK_ERROR); setRefreshing(false); return; } // do not refresh if in search if (mSearchTerm != null && mSearchTerm.length() > 0) { setRefreshing(false); return; } if (!mIsRefreshing) { mIsRefreshing = true; updateEmptyView(EmptyViewMessageType.LOADING); setRefreshing(true); FetchMediaListPayload payload = new FetchMediaListPayload(mSite, loadMore); mDispatcher.dispatch(MediaActionBuilder.newFetchMediaListAction(payload)); } } private void handleFetchAllMediaSuccess(OnMediaListFetched event) { MediaGridAdapter adapter = (MediaGridAdapter) mRecycler.getAdapter(); List<MediaModel> mediaList = mMediaStore.getAllSiteMedia(mSite); adapter.setMediaList(mediaList); mHasRetrievedAllMedia = !event.canLoadMore; adapter.setHasRetrievedAll(mHasRetrievedAllMedia); mIsRefreshing = false; // the activity may be gone by the time this finishes, so check for it if (getActivity() != null && isVisible()) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { refreshSpinnerAdapter(); updateEmptyView(EmptyViewMessageType.NO_CONTENT); mSwipeToRefreshHelper.setRefreshing(false); } }); } } private void handleFetchAllMediaError(MediaErrorType errorType) { AppLog.e(AppLog.T.MEDIA, "Media error occurred: " + errorType); final boolean isPermissionError = (errorType == MediaErrorType.AUTHORIZATION_REQUIRED); if (getActivity() != null) { if (!isPermissionError) { ToastUtils.showToast(getActivity(), getString(R.string.error_refresh_media), Duration.LONG); } else { if (mEmptyView == null || mEmptyView.getVisibility() != View.VISIBLE) { ToastUtils.showToast(getActivity(), getString( R.string.media_error_no_permission)); } } } MediaGridAdapter adapter = (MediaGridAdapter) mRecycler.getAdapter(); mHasRetrievedAllMedia = true; adapter.setHasRetrievedAll(true); // the activity may be gone by the time we get this, so check for it if (getActivity() != null && MediaGridFragment.this.isVisible()) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { mIsRefreshing = false; mSwipeToRefreshHelper.setRefreshing(false); if (isPermissionError) { updateEmptyView(EmptyViewMessageType.PERMISSION_ERROR); } else { updateEmptyView(EmptyViewMessageType.GENERIC_ERROR); } } }); } } private final class ActionModeCallback implements ActionMode.Callback { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mActionMode = mode; int selectCount = mGridAdapter.getSelectedItemCount(); MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.media_multiselect, menu); setSwipeToRefreshEnabled(false); mGridAdapter.setInMultiSelect(true); updateActionModeTitle(selectCount); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { int i = item.getItemId(); if (i == R.id.media_multiselect_actionbar_trash) { handleMultiSelectDelete(); return true; } return true; } @Override public void onDestroyActionMode(ActionMode mode) { setSwipeToRefreshEnabled(true); mGridAdapter.setInMultiSelect(false); mActionMode = null; setFilterEnabled(mGridAdapter.getSelectedItems().size() == 0); } } }