/** * technoXist * * Copyright (c) 2014-2015 Suyash Bhatt * * 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, either version 3 of the License, or * (at your option) any later version. * * 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/>. */ package com.technoxist.fragment; import android.app.LoaderManager; import android.content.ContentUris; import android.content.Context; import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.view.MenuItemCompat; import android.support.v7.widget.SearchView; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; import android.widget.CompoundButton; import android.widget.ListView; import android.support.v7.widget.SwitchCompat; import android.widget.Toast; import com.technoxist.Constants; import com.technoxist.R; import com.technoxist.adapter.EntriesCursorAdapter; import com.technoxist.provider.FeedData; import com.technoxist.provider.FeedData.EntryColumns; import com.technoxist.provider.FeedDataContentProvider; import com.technoxist.service.FetcherService; import com.technoxist.utils.PrefUtils; import java.util.Date; public class EntriesListFragment extends SwipeRefreshListFragment { private static final String STATE_URI = "STATE_URI"; private static final String STATE_SHOW_FEED_INFO = "STATE_SHOW_FEED_INFO"; private static final String STATE_LIST_DISPLAY_DATE = "STATE_LIST_DISPLAY_DATE"; private static final int ENTRIES_LOADER_ID = 1; private static final int NEW_ENTRIES_NUMBER_LOADER_ID = 2; private Uri mUri; private boolean mShowFeedInfo = false; private EntriesCursorAdapter mEntriesCursorAdapter; private SearchView mSearchView; private long mListDisplayDate = new Date().getTime(); private int mNewEntriesNumber, mOldUnreadEntriesNumber = -1; private boolean mAutoRefreshDisplayDate = false; private final OnSharedPreferenceChangeListener mPrefListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (PrefUtils.SHOW_READ.equals(key)) { getLoaderManager().restartLoader(ENTRIES_LOADER_ID, null, mEntriesLoader); } else if (PrefUtils.IS_REFRESHING.equals(key)) { refreshSwipeProgress(); } } }; private LoaderManager.LoaderCallbacks<Cursor> mEntriesLoader = new LoaderManager.LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String entriesOrder = PrefUtils.getBoolean(PrefUtils.DISPLAY_OLDEST_FIRST, false) ? Constants.DB_ASC : Constants.DB_DESC; String where = "(" + EntryColumns.FETCH_DATE + Constants.DB_IS_NULL + Constants.DB_OR + EntryColumns.FETCH_DATE + "<=" + mListDisplayDate + ')'; if (!FeedData.shouldShowReadEntries(mUri)) { where += Constants.DB_AND + EntryColumns.WHERE_UNREAD; } CursorLoader cursorLoader = new CursorLoader(getActivity(), mUri, null, where, null, EntryColumns.DATE + entriesOrder); cursorLoader.setUpdateThrottle(150); return cursorLoader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { mEntriesCursorAdapter.swapCursor(data); } @Override public void onLoaderReset(Loader<Cursor> loader) { mEntriesCursorAdapter.swapCursor(Constants.EMPTY_CURSOR); } }; private LoaderManager.LoaderCallbacks<Cursor> mEntriesNumberLoader = new LoaderManager.LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { CursorLoader cursorLoader = new CursorLoader(getActivity(), mUri, new String[]{"SUM(" + EntryColumns.FETCH_DATE + '>' + mListDisplayDate + ")", "SUM(" + EntryColumns.FETCH_DATE + "<=" + mListDisplayDate + Constants.DB_AND + EntryColumns.WHERE_UNREAD + ")"}, null, null, null); cursorLoader.setUpdateThrottle(150); return cursorLoader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { data.moveToFirst(); mNewEntriesNumber = data.getInt(0); mOldUnreadEntriesNumber = data.getInt(1); if (mAutoRefreshDisplayDate && mNewEntriesNumber != 0 && mOldUnreadEntriesNumber == 0) { mListDisplayDate = new Date().getTime(); getLoaderManager().restartLoader(ENTRIES_LOADER_ID, null, mEntriesLoader); getLoaderManager().restartLoader(NEW_ENTRIES_NUMBER_LOADER_ID, null, mEntriesNumberLoader); } else { refreshUI(); } mAutoRefreshDisplayDate = false; } @Override public void onLoaderReset(Loader<Cursor> loader) { } }; private class SwipeGestureListener extends SimpleOnGestureListener implements OnTouchListener { private final GestureDetector mGestureDetector; public SwipeGestureListener(Context context) { mGestureDetector = new GestureDetector(context, this); } @Override public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); } } @Override public void onCreate(Bundle savedInstanceState) { setHasOptionsMenu(true); super.onCreate(savedInstanceState); if (savedInstanceState != null) { mUri = savedInstanceState.getParcelable(STATE_URI); mShowFeedInfo = savedInstanceState.getBoolean(STATE_SHOW_FEED_INFO); mListDisplayDate = savedInstanceState.getLong(STATE_LIST_DISPLAY_DATE); mEntriesCursorAdapter = new EntriesCursorAdapter(getActivity(), mUri, Constants.EMPTY_CURSOR, mShowFeedInfo); } } @Override public void onStart() { super.onStart(); refreshSwipeProgress(); PrefUtils.registerOnPrefChangeListener(mPrefListener); if (mUri != null) { // If the list is empty when we are going back here, try with the last display date if (mNewEntriesNumber != 0 && mOldUnreadEntriesNumber == 0) { mListDisplayDate = new Date().getTime(); } else { mAutoRefreshDisplayDate = true; // We will try to update the list after if necessary } getLoaderManager().restartLoader(ENTRIES_LOADER_ID, null, mEntriesLoader); getLoaderManager().restartLoader(NEW_ENTRIES_NUMBER_LOADER_ID, null, mEntriesNumberLoader); } } @Override public View inflateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_entry_list, container, true); if (mEntriesCursorAdapter != null) { setListAdapter(mEntriesCursorAdapter); } ListView mListView = (ListView) rootView.findViewById(android.R.id.list); mListView.setFastScrollEnabled(false); mListView.setOnTouchListener(new SwipeGestureListener(getActivity())); mListView.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_up_right)); mSearchView = (SearchView) rootView.findViewById(R.id.searchView); if (savedInstanceState != null) { refreshUI(); // To hide/show the search bar } mSearchView.post(new Runnable() { // Do this AFTER the text has been restored from saveInstanceState @Override public void run() { mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0); return false; } @Override public boolean onQueryTextChange(String s) { setData(EntryColumns.SEARCH_URI(s), true); return false; } }); } }); return rootView; } @Override public void onStop() { PrefUtils.unregisterOnPrefChangeListener(mPrefListener); super.onStop(); } @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable(STATE_URI, mUri); outState.putBoolean(STATE_SHOW_FEED_INFO, mShowFeedInfo); outState.putLong(STATE_LIST_DISPLAY_DATE, mListDisplayDate); super.onSaveInstanceState(outState); } @Override public void onRefresh() { startRefresh(); } @Override public void onListItemClick(ListView listView, View view, int position, long id) { startActivity(new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId(mUri, id))); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.clear(); // This is needed to remove a bug on Android 4.0.3 inflater.inflate(R.menu.entry_list, menu); MenuItem switchItem = menu.findItem(R.id.switchRead); View switchView = MenuItemCompat.getActionView(switchItem); SwitchCompat mswitch = (SwitchCompat) switchView.findViewById(R.id.switch_unread); mswitch.setThumbResource(R.drawable.switch_thumb); mswitch.setTrackResource(R.drawable.switch_track); if (!PrefUtils.getBoolean(PrefUtils.SHOW_READ, true)) { mswitch.setChecked(true); } else { mswitch.setChecked(false); } mswitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // The switch is enabled PrefUtils.putBoolean(PrefUtils.SHOW_READ, false); Toast.makeText(getActivity(), R.string.show_unread, Toast.LENGTH_SHORT).show(); } else { // The toggle is disabled PrefUtils.putBoolean(PrefUtils.SHOW_READ, true); Toast.makeText(getActivity(), R.string.show_all, Toast.LENGTH_SHORT).show(); } } }); if (EntryColumns.FAVORITES_CONTENT_URI.equals(mUri)) { menu.findItem(R.id.menu_refresh).setVisible(false); } else if (mUri != null && FeedDataContentProvider.URI_MATCHER.match(mUri) == FeedDataContentProvider.URI_SEARCH) { menu.findItem(R.id.menu_share_starred).setVisible(false); } else { menu.findItem(R.id.menu_share_starred).setVisible(false); } super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_share_starred: { String starredList = ""; Cursor cursor = mEntriesCursorAdapter.getCursor(); if (cursor != null && !cursor.isClosed()) { int titlePos = cursor.getColumnIndex(EntryColumns.TITLE); int linkPos = cursor.getColumnIndex(EntryColumns.LINK); if (cursor.moveToFirst()) { do { starredList += cursor.getString(titlePos) + "\n" + cursor.getString(linkPos) + "\n\n"; } while (cursor.moveToNext()); } startActivity(Intent.createChooser( new Intent(Intent.ACTION_SEND).putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_favorites_title)) .putExtra(Intent.EXTRA_TEXT, starredList).setType(Constants.MIMETYPE_TEXT_PLAIN), getString(R.string.menu_share) )); } return true; } case R.id.menu_refresh: { startRefresh(); return true; } case R.id.menu_all_read: { mEntriesCursorAdapter.markAllAsRead(mListDisplayDate); // If we are on "all items" uri, we can remove the notification here if (EntryColumns.CONTENT_URI.equals(mUri) && Constants.NOTIF_MGR != null) { Constants.NOTIF_MGR.cancel(0); } return true; } } return super.onOptionsItemSelected(item); } private void startRefresh() { if (!PrefUtils.getBoolean(PrefUtils.IS_REFRESHING, false)) { if (FeedDataContentProvider.URI_MATCHER.match(mUri) == FeedDataContentProvider.URI_ENTRIES_FOR_FEED) { getActivity().startService(new Intent(getActivity(), FetcherService.class).setAction(FetcherService.ACTION_REFRESH_FEEDS).putExtra(Constants.FEED_ID, mUri.getPathSegments().get(1))); } else { getActivity().startService(new Intent(getActivity(), FetcherService.class).setAction(FetcherService.ACTION_REFRESH_FEEDS)); } } } public Uri getUri() { return mUri; } public String getCurrentSearch() { return mSearchView == null ? null : mSearchView.getQuery().toString(); } public void setData(Uri uri, boolean showFeedInfo) { mUri = uri; mShowFeedInfo = showFeedInfo; mEntriesCursorAdapter = new EntriesCursorAdapter(getActivity(), mUri, Constants.EMPTY_CURSOR, mShowFeedInfo); setListAdapter(mEntriesCursorAdapter); mListDisplayDate = new Date().getTime(); if (mUri != null) { getLoaderManager().restartLoader(ENTRIES_LOADER_ID, null, mEntriesLoader); getLoaderManager().restartLoader(NEW_ENTRIES_NUMBER_LOADER_ID, null, mEntriesNumberLoader); } refreshUI(); } private void refreshUI() { if (mUri != null && FeedDataContentProvider.URI_MATCHER.match(mUri) == FeedDataContentProvider.URI_SEARCH) { mSearchView.setVisibility(View.VISIBLE); } else { mSearchView.setVisibility(View.GONE); } if (mUri != null && FeedDataContentProvider.URI_MATCHER.match(mUri) != FeedDataContentProvider.URI_FAVORITES) { enableSwipe(); } else { disableSwipe(); } if (mNewEntriesNumber > 0) { mNewEntriesNumber = 0; mListDisplayDate = new Date().getTime(); refreshUI(); if (mUri != null) { getLoaderManager().restartLoader(ENTRIES_LOADER_ID, null, mEntriesLoader); getLoaderManager().restartLoader(NEW_ENTRIES_NUMBER_LOADER_ID, null, mEntriesNumberLoader); } } } private void refreshSwipeProgress() { if (PrefUtils.getBoolean(PrefUtils.IS_REFRESHING, false)) { showSwipeProgress(); } else { hideSwipeProgress(); } } }