package kr.kdev.dg1s.biowiki.ui.media; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView.RecyclerListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.DatePicker; import android.widget.LinearLayout; import android.widget.TextView; import com.actionbarsherlock.internal.widget.IcsAdapterView; import com.actionbarsherlock.internal.widget.IcsAdapterView.OnItemSelectedListener; import com.android.volley.VolleyError; import com.android.volley.toolbox.ImageLoader.ImageContainer; import com.android.volley.toolbox.ImageLoader.ImageListener; import org.xmlrpc.android.ApiHelper; import org.xmlrpc.android.ApiHelper.SyncMediaLibraryTask.Callback; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.List; import kr.kdev.dg1s.biowiki.BioWiki; import kr.kdev.dg1s.biowiki.R; import kr.kdev.dg1s.biowiki.models.Blog; import kr.kdev.dg1s.biowiki.ui.BWActionBarActivity; import kr.kdev.dg1s.biowiki.ui.CheckableFrameLayout; import kr.kdev.dg1s.biowiki.ui.CustomSpinner; import kr.kdev.dg1s.biowiki.ui.MultiSelectGridView; import kr.kdev.dg1s.biowiki.ui.MultiSelectGridView.MultiSelectListener; import kr.kdev.dg1s.biowiki.ui.PullToRefreshHelper; import kr.kdev.dg1s.biowiki.ui.PullToRefreshHelper.RefreshListener; import kr.kdev.dg1s.biowiki.ui.media.MediaGridAdapter.MediaGridAdapterCallback; import kr.kdev.dg1s.biowiki.util.NetworkUtils; import kr.kdev.dg1s.biowiki.util.ToastUtils; import uk.co.senab.actionbarpulltorefresh.extras.actionbarsherlock.PullToRefreshLayout; /** * The grid displaying the media items. * It appears as 2 columns on phone and 1 column on tablet (essentially a listview) */ public class MediaGridFragment extends Fragment implements OnItemClickListener, MediaGridAdapterCallback, RecyclerListener, MultiSelectListener { private static final String BUNDLE_CHECKED_STATES = "BUNDLE_CHECKED_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_RETREIEVED_ALL_MEDIA = "BUNDLE_HAS_RETREIEVED_ALL_MEDIA"; private static final String BUNDLE_FILTER = "BUNDLE_FILTER"; private static final String BUNDLE_DATE_FILTER_SET = "BUNDLE_DATE_FILTER_SET"; private static final String BUNDLE_DATE_FILTER_VISIBLE = "BUNDLE_DATE_FILTER_VISIBLE"; private static final String BUNDLE_DATE_FILTER_START_YEAR = "BUNDLE_DATE_FILTER_START_YEAR"; private static final String BUNDLE_DATE_FILTER_START_MONTH = "BUNDLE_DATE_FILTER_START_MONTH"; private static final String BUNDLE_DATE_FILTER_START_DAY = "BUNDLE_DATE_FILTER_START_DAY"; private static final String BUNDLE_DATE_FILTER_END_YEAR = "BUNDLE_DATE_FILTER_END_YEAR"; private static final String BUNDLE_DATE_FILTER_END_MONTH = "BUNDLE_DATE_FILTER_END_MONTH"; private static final String BUNDLE_DATE_FILTER_END_DAY = "BUNDLE_DATE_FILTER_END_DAY"; private Filter mFilter = Filter.ALL; private String[] mFiltersText; private MultiSelectGridView mGridView; private MediaGridAdapter mGridAdapter; private MediaGridListener mListener; private ArrayList<String> mCheckedItems; private boolean mIsRefreshing = false; private boolean mHasRetrievedAllMedia = false; private String mSearchTerm; private View mSpinnerContainer; private TextView mResultView; private LinearLayout mEmptyView; private TextView mEmptyViewTitle; private CustomSpinner mSpinner; private PullToRefreshHelper mPullToRefreshHelper; private int mOldMediaSyncOffset = 0; private boolean mIsDateFilterSet = false; private boolean mSpinnerHasLaunched = false; private final OnItemSelectedListener mFilterSelectedListener = new OnItemSelectedListener() { @Override public void onItemSelected(IcsAdapterView<?> parent, View view, int position, long id) { // need this to stop the bug where onItemSelected is called during initialization, before user input if (!mSpinnerHasLaunched) { return; } if (position == Filter.CUSTOM_DATE.ordinal()) { mIsDateFilterSet = true; } setFilter(Filter.getFilter(position)); } @Override public void onNothingSelected(IcsAdapterView<?> parent) { } }; private int mStartYear, mStartMonth, mStartDay, mEndYear, mEndMonth, mEndDay; private AlertDialog mDatePickerDialog; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); mCheckedItems = new ArrayList<String>(); mFiltersText = new String[Filter.values().length]; mGridAdapter = new MediaGridAdapter(getActivity(), null, 0, mCheckedItems, MediaImageLoader.getInstance()); mGridAdapter.setCallback(this); View view = inflater.inflate(R.layout.media_grid_fragment, container); mGridView = (MultiSelectGridView) view.findViewById(R.id.media_gridview); mGridView.setOnItemClickListener(this); mGridView.setRecyclerListener(this); mGridView.setMultiSelectListener(this); mGridView.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 = (CustomSpinner) view.findViewById(R.id.media_filter_spinner); mSpinner.setOnItemSelectedListener(mFilterSelectedListener); mSpinner.setOnItemSelectedEvenIfUnchangedListener(mFilterSelectedListener); mSpinnerContainer = view.findViewById(R.id.media_filter_spinner_container); mSpinnerContainer.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!isInMultiSelect()) { mSpinnerHasLaunched = true; mSpinner.performClick(); } } }); restoreState(savedInstanceState); setupSpinnerAdapter(); // pull to refresh setup mPullToRefreshHelper = new PullToRefreshHelper(getActivity(), (PullToRefreshLayout) view.findViewById(R.id.ptr_layout), new RefreshListener() { @Override public void onRefreshStarted(View view) { if (getActivity() == null || !NetworkUtils.checkConnection(getActivity())) { mPullToRefreshHelper.setRefreshing(false); return; } refreshMediaFromServer(0, false); } }, LinearLayout.class ); return view; } private void restoreState(Bundle savedInstanceState) { if (savedInstanceState == null) return; boolean isInMultiSelectMode = savedInstanceState.getBoolean(BUNDLE_IN_MULTI_SELECT_MODE); if (savedInstanceState.containsKey(BUNDLE_CHECKED_STATES)) { mCheckedItems.addAll(savedInstanceState.getStringArrayList(BUNDLE_CHECKED_STATES)); if (isInMultiSelectMode) { mListener.onMultiSelectChange(mCheckedItems.size()); onMultiSelectChange(mCheckedItems.size()); } mGridView.setMultiSelectModeActive(isInMultiSelectMode); } mGridView.setSelection(savedInstanceState.getInt(BUNDLE_SCROLL_POSITION, 0)); mHasRetrievedAllMedia = savedInstanceState.getBoolean(BUNDLE_HAS_RETREIEVED_ALL_MEDIA, false); mFilter = Filter.getFilter(savedInstanceState.getInt(BUNDLE_FILTER)); mIsDateFilterSet = savedInstanceState.getBoolean(BUNDLE_DATE_FILTER_SET, false); mStartDay = savedInstanceState.getInt(BUNDLE_DATE_FILTER_START_DAY); mStartMonth = savedInstanceState.getInt(BUNDLE_DATE_FILTER_START_MONTH); mStartYear = savedInstanceState.getInt(BUNDLE_DATE_FILTER_START_YEAR); mEndDay = savedInstanceState.getInt(BUNDLE_DATE_FILTER_END_DAY); mEndMonth = savedInstanceState.getInt(BUNDLE_DATE_FILTER_END_MONTH); mEndYear = savedInstanceState.getInt(BUNDLE_DATE_FILTER_END_YEAR); boolean datePickerShowing = savedInstanceState.getBoolean(BUNDLE_DATE_FILTER_VISIBLE); if (datePickerShowing) showDatePicker(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); saveState(outState); } private void saveState(Bundle outState) { outState.putStringArrayList(BUNDLE_CHECKED_STATES, mCheckedItems); outState.putInt(BUNDLE_SCROLL_POSITION, mGridView.getFirstVisiblePosition()); outState.putBoolean(BUNDLE_HAS_RETREIEVED_ALL_MEDIA, mHasRetrievedAllMedia); outState.putBoolean(BUNDLE_IN_MULTI_SELECT_MODE, isInMultiSelect()); outState.putInt(BUNDLE_FILTER, mFilter.ordinal()); outState.putBoolean(BUNDLE_DATE_FILTER_SET, mIsDateFilterSet); outState.putBoolean(BUNDLE_DATE_FILTER_VISIBLE, (mDatePickerDialog != null && mDatePickerDialog.isShowing())); outState.putInt(BUNDLE_DATE_FILTER_START_DAY, mStartDay); outState.putInt(BUNDLE_DATE_FILTER_START_MONTH, mStartMonth); outState.putInt(BUNDLE_DATE_FILTER_START_YEAR, mStartYear); outState.putInt(BUNDLE_DATE_FILTER_END_DAY, mEndDay); outState.putInt(BUNDLE_DATE_FILTER_END_MONTH, mEndMonth); outState.putInt(BUNDLE_DATE_FILTER_END_YEAR, mEndYear); } private void setupSpinnerAdapter() { if (getActivity() == null || BioWiki.getCurrentBlog() == null) return; updateFilterText(); Context context = ((BWActionBarActivity) getActivity()).getSupportActionBar().getThemedContext(); ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, R.layout.sherlock_spinner_dropdown_item, mFiltersText); mSpinner.setAdapter(adapter); mSpinner.setSelection(mFilter.ordinal()); } public void refreshSpinnerAdapter() { updateFilterText(); updateSpinnerAdapter(); setFilter(mFilter); } void resetSpinnerAdapter() { setFiltersText(0, 0, 0); updateSpinnerAdapter(); } private void updateFilterText() { if (BioWiki.currentBlog == null) return; String blogId = String.valueOf(BioWiki.getCurrentBlog().getLocalTableBlogId()); int countAll = BioWiki.wpDB.getMediaCountAll(blogId); int countImages = BioWiki.wpDB.getMediaCountImages(blogId); int countUnattached = BioWiki.wpDB.getMediaCountUnattached(blogId); setFiltersText(countAll, countImages, countUnattached); } private void setFiltersText(int countAll, int countImages, int countUnattached) { mFiltersText[0] = getResources().getString(R.string.all) + " (" + countAll + ")"; mFiltersText[1] = getResources().getString(R.string.images) + " (" + countImages + ")"; mFiltersText[2] = getResources().getString(R.string.unattached) + " (" + countUnattached + ")"; mFiltersText[3] = getResources().getString(R.string.custom_date) + "..."; } private void updateSpinnerAdapter() { ArrayAdapter<String> adapter = (ArrayAdapter<String>) mSpinner.getAdapter(); if (adapter != null) { adapter.notifyDataSetChanged(); } } @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 onResume() { super.onResume(); if (!NetworkUtils.isNetworkAvailable(this.getActivity())) mHasRetrievedAllMedia = true; refreshSpinnerAdapter(); refreshMediaFromDB(); } public void refreshMediaFromDB() { setFilter(mFilter); if (mGridAdapter.getDataCount() == 0 && !mHasRetrievedAllMedia) { refreshMediaFromServer(0, true); } } public void refreshMediaFromServer(int offset, final boolean auto) { // do not refresh if custom date filter is shown if (BioWiki.getCurrentBlog() == null || mFilter == Filter.CUSTOM_DATE) { return; } // do not refresh if in search if (mSearchTerm != null && mSearchTerm.length() > 0) { return; } if (offset == 0 || !mIsRefreshing) { if (offset == mOldMediaSyncOffset) { // we're pulling the same data again for some reason. Pull from the beginning. offset = 0; } mOldMediaSyncOffset = offset; mIsRefreshing = true; mListener.onMediaItemListDownloadStart(); mGridAdapter.setRefreshing(true); List<Object> apiArgs = new ArrayList<Object>(); apiArgs.add(BioWiki.getCurrentBlog()); Callback callback = new Callback() { // refresh db from server. If returned count is 0, we've retrieved all the media. // stop retrieving until the user manually refreshes @Override public void onSuccess(int count) { MediaGridAdapter adapter = (MediaGridAdapter) mGridView.getAdapter(); mHasRetrievedAllMedia = (count == 0); adapter.setHasRetrievedAll(mHasRetrievedAllMedia); mIsRefreshing = false; // the activity may be gone by the time this finishes, so check for it if (getActivity() != null && MediaGridFragment.this.isVisible()) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { refreshSpinnerAdapter(); setFilter(mFilter); if (!auto) mGridView.setSelection(0); mListener.onMediaItemListDownloaded(); mGridAdapter.setRefreshing(false); mPullToRefreshHelper.setRefreshing(false); } }); } } @Override public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) { if (errorType != ApiHelper.ErrorType.NO_ERROR) { if (getActivity() != null) { String message = errorType == ApiHelper.ErrorType.NO_UPLOAD_FILES_CAP ? getString( R.string.media_error_no_permission) : getString(R.string.error_refresh_media); ToastUtils.showToast(getActivity(), message, ToastUtils.Duration.LONG); } MediaGridAdapter adapter = (MediaGridAdapter) mGridView.getAdapter(); mHasRetrievedAllMedia = true; adapter.setHasRetrievedAll(mHasRetrievedAllMedia); } // the activity may be cone 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; mListener.onMediaItemListDownloaded(); mGridAdapter.setRefreshing(false); mPullToRefreshHelper.setRefreshing(false); } }); } } }; ApiHelper.SyncMediaLibraryTask getMediaTask = new ApiHelper.SyncMediaLibraryTask(offset, mFilter, callback); getMediaTask.execute(apiArgs); } } public void search(String searchTerm) { mSearchTerm = searchTerm; Blog blog = BioWiki.getCurrentBlog(); if (blog != null) { String blogId = String.valueOf(blog.getLocalTableBlogId()); Cursor cursor = BioWiki.wpDB.getMediaFilesForBlog(blogId, searchTerm); mGridAdapter.changeCursor(cursor); } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Cursor cursor = ((MediaGridAdapter) parent.getAdapter()).getCursor(); String mediaId = cursor.getString(cursor.getColumnIndex("mediaId")); mListener.onMediaItemSelected(mediaId); } public void setFilterVisibility(int visibility) { if (mSpinner != null) mSpinner.setVisibility(visibility); } private void setEmptyViewVisible(boolean visible) { setEmptyViewVisible(visible, -1); } private void setEmptyViewVisible(boolean visible, int messageId) { if (visible) { mGridView.setVisibility(View.GONE); mEmptyView.setVisibility(View.VISIBLE); if (messageId != -1) { mEmptyViewTitle.setText(getResources().getString(messageId)); } } else { mEmptyView.setVisibility(View.GONE); mGridView.setVisibility(View.VISIBLE); } } public void setFilter(Filter filter) { mFilter = filter; Cursor cursor = filterItems(mFilter); if (filter != Filter.CUSTOM_DATE || cursor == null || cursor.getCount() == 0) { mResultView.setVisibility(View.GONE); } if (cursor != null && cursor.getCount() != 0) { mGridAdapter.swapCursor(cursor); setEmptyViewVisible(false); } else { if (filter != Filter.CUSTOM_DATE) { setEmptyViewVisible(true, R.string.media_empty_list); } } } Cursor setDateFilter() { Blog blog = BioWiki.getCurrentBlog(); if (blog == null) return null; String blogId = String.valueOf(blog.getLocalTableBlogId()); GregorianCalendar startDate = new GregorianCalendar(mStartYear, mStartMonth, mStartDay); GregorianCalendar endDate = new GregorianCalendar(mEndYear, mEndMonth, mEndDay); long one_day = 24 * 60 * 60 * 1000; Cursor cursor = BioWiki.wpDB.getMediaFilesForBlog(blogId, startDate.getTimeInMillis(), endDate.getTimeInMillis() + one_day); mGridAdapter.swapCursor(cursor); if (cursor != null && cursor.moveToFirst()) { mResultView.setVisibility(View.VISIBLE); setEmptyViewVisible(false); SimpleDateFormat fmt = new SimpleDateFormat("dd-MMM-yyyy"); fmt.setCalendar(startDate); String formattedStart = fmt.format(startDate.getTime()); String formattedEnd = fmt.format(endDate.getTime()); // TODO: replace hard-coded text with string resource mResultView.setText("Displaying media from " + formattedStart + " to " + formattedEnd); return cursor; } else { setEmptyViewVisible(true, R.string.media_empty_list_custom_date); } return null; } private Cursor filterItems(Filter filter) { Blog blog = BioWiki.getCurrentBlog(); if (blog == null) return null; String blogId = String.valueOf(blog.getLocalTableBlogId()); switch (filter) { case ALL: return BioWiki.wpDB.getMediaFilesForBlog(blogId); case IMAGES: return BioWiki.wpDB.getMediaImagesForBlog(blogId); case UNATTACHED: return BioWiki.wpDB.getMediaUnattachedForBlog(blogId); case CUSTOM_DATE: // show date picker only when the user clicks on the spinner, not when we are doing syncing if (mIsDateFilterSet) { mIsDateFilterSet = false; showDatePicker(); } else { return setDateFilter(); } break; } return null; } void showDatePicker() { // Inflate your custom layout containing 2 DatePickers LayoutInflater inflater = getActivity().getLayoutInflater(); View customView = inflater.inflate(R.layout.date_range_dialog, null); // Define your date pickers final DatePicker dpStartDate = (DatePicker) customView.findViewById(R.id.dpStartDate); final DatePicker dpEndDate = (DatePicker) customView.findViewById(R.id.dpEndDate); // Build the dialog AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(customView); // Set the view of the dialog to your custom layout builder.setTitle("Select start and end date"); builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mStartYear = dpStartDate.getYear(); mStartMonth = dpStartDate.getMonth(); mStartDay = dpStartDate.getDayOfMonth(); mEndYear = dpEndDate.getYear(); mEndMonth = dpEndDate.getMonth(); mEndDay = dpEndDate.getDayOfMonth(); setDateFilter(); dialog.dismiss(); } }); // Create and show the dialog mDatePickerDialog = builder.create(); mDatePickerDialog.show(); } @Override public void fetchMoreData(int offset) { if (!mHasRetrievedAllMedia) refreshMediaFromServer(offset, true); } @Override public void onMovedToScrapHeap(View view) { // cancel image fetch requests if the view has been moved to recycler. View imageView = view.findViewById(R.id.media_grid_item_image); if (imageView != null) { // this tag is set in the MediaGridAdapter class String tag = (String) imageView.getTag(); if (tag != null && tag.startsWith("http")) { // need a listener to cancel request, even if the listener does nothing ImageContainer container = BioWiki.imageLoader.get(tag, new ImageListener() { @Override public void onErrorResponse(VolleyError error) { } @Override public void onResponse(ImageContainer response, boolean isImmediate) { } }); container.cancelRequest(); } } CheckableFrameLayout layout = (CheckableFrameLayout) view.findViewById(R.id.media_grid_frame_layout); if (layout != null) { layout.setOnCheckedChangeListener(null); } } @Override public void onMultiSelectChange(int count) { if (count == 0) { // enable filtering when not in multiselect mSpinner.setEnabled(true); mSpinnerContainer.setEnabled(true); mSpinnerContainer.setVisibility(View.VISIBLE); } else { // disable filtering on multiselect mSpinner.setEnabled(false); mSpinnerContainer.setEnabled(false); mSpinnerContainer.setVisibility(View.GONE); } mListener.onMultiSelectChange(count); } @Override public boolean isInMultiSelect() { return mGridView.isInMultiSelectMode(); } public ArrayList<String> getCheckedItems() { return mCheckedItems; } public void clearCheckedItems() { mGridView.cancelSelection(); } @Override public void onRetryUpload(String mediaId) { mListener.onRetryUpload(mediaId); } public boolean hasRetrievedAllMediaFromServer() { return mHasRetrievedAllMedia; } /* * called by activity when blog is changed */ protected void reset() { mCheckedItems.clear(); mGridView.setSelection(0); mGridView.requestFocusFromTouch(); mGridView.setSelection(0); mGridAdapter.setImageLoader(MediaImageLoader.getInstance()); mGridAdapter.changeCursor(null); resetSpinnerAdapter(); mHasRetrievedAllMedia = false; } public void removeFromMultiSelect(String mediaId) { if (isInMultiSelect()) { mCheckedItems.remove(mediaId); mListener.onMultiSelectChange(mCheckedItems.size()); onMultiSelectChange(mCheckedItems.size()); } } public void setRefreshing(boolean refreshing) { mPullToRefreshHelper.setRefreshing(refreshing); } public void setPullToRefreshEnabled(boolean enabled) { mPullToRefreshHelper.setEnabled(enabled); } public enum Filter { ALL, IMAGES, UNATTACHED, CUSTOM_DATE; public static Filter getFilter(int filterPos) { if (filterPos > Filter.values().length) return ALL; else return Filter.values()[filterPos]; } } public interface MediaGridListener { public void onMediaItemListDownloadStart(); public void onMediaItemListDownloaded(); public void onMediaItemSelected(String mediaId); public void onMultiSelectChange(int count); public void onRetryUpload(String mediaId); } }