/*
* Copyright (c) 2015, Nils Braden
*
* This file is part of ttrss-reader-fork. 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 org.ttrssreader.gui.fragments;
import org.ttrssreader.R;
import org.ttrssreader.controllers.Controller;
import org.ttrssreader.controllers.DBHelper;
import org.ttrssreader.gui.FeedHeadlineActivity;
import org.ttrssreader.gui.MenuActivity;
import org.ttrssreader.gui.TextInputAlert;
import org.ttrssreader.gui.dialogs.ReadStateDialog;
import org.ttrssreader.gui.dialogs.YesNoUpdaterDialog;
import org.ttrssreader.gui.interfaces.IItemSelectedListener.TYPE;
import org.ttrssreader.gui.interfaces.TextInputAlertCallback;
import org.ttrssreader.gui.view.MyGestureDetector;
import org.ttrssreader.model.FeedHeadlineAdapter;
import org.ttrssreader.model.ListContentProvider;
import org.ttrssreader.model.pojos.Article;
import org.ttrssreader.model.pojos.Category;
import org.ttrssreader.model.pojos.Feed;
import org.ttrssreader.model.updaters.ArticleReadStateUpdater;
import org.ttrssreader.model.updaters.IUpdatable;
import org.ttrssreader.model.updaters.NoteUpdater;
import org.ttrssreader.model.updaters.PublishedStateUpdater;
import org.ttrssreader.model.updaters.ReadStateUpdater;
import org.ttrssreader.model.updaters.StarredStateUpdater;
import org.ttrssreader.model.updaters.UnsubscribeUpdater;
import org.ttrssreader.model.updaters.Updater;
import org.ttrssreader.utils.AsyncTask;
import android.app.Activity;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.net.Uri.Builder;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import java.util.ArrayList;
import java.util.List;
public class FeedHeadlineListFragment extends MainListFragment implements TextInputAlertCallback {
@SuppressWarnings("unused")
private static final String TAG = FeedHeadlineListFragment.class.getSimpleName();
private static final TYPE THIS_TYPE = TYPE.FEEDHEADLINE;
public static final String FRAGMENT = "FEEDHEADLINE_FRAGMENT";
public static final String FEED_CAT_ID = "FEED_CAT_ID";
public static final String FEED_ID = "ARTICLE_FEED_ID";
public static final String ARTICLE_ID = "ARTICLE_ID";
public static final String FEED_SELECT_ARTICLES = "FEED_SELECT_ARTICLES";
private static final int MARK_GROUP = 200;
private static final int MARK_READ = MARK_GROUP + 1;
private static final int MARK_STAR = MARK_GROUP + 2;
private static final int MARK_PUBLISH = MARK_GROUP + 3;
private static final int MARK_NOTE = MARK_GROUP + 4;
private static final int MARK_ABOVE_READ = MARK_GROUP + 5;
private static final int SHARE = MARK_GROUP + 6;
private int categoryId = Integer.MIN_VALUE;
private int feedId = Integer.MIN_VALUE;
private boolean selectArticlesForCategory = false;
private Uri headlineUri;
public static FeedHeadlineListFragment newInstance(int id, int categoryId, boolean selectArticles) {
FeedHeadlineListFragment detail = new FeedHeadlineListFragment();
detail.categoryId = categoryId;
detail.feedId = id;
detail.selectArticlesForCategory = selectArticles;
detail.setRetainInstance(true);
return detail;
}
@Override
public void onCreate(Bundle instance) {
if (instance != null) {
categoryId = instance.getInt(FEED_CAT_ID);
feedId = instance.getInt(FEED_ID);
selectArticlesForCategory = instance.getBoolean(FEED_SELECT_ARTICLES);
}
if (feedId > 0) Controller.getInstance().lastOpenedFeeds.add(feedId);
Controller.getInstance().lastOpenedArticles.clear();
setHasOptionsMenu(true);
super.onCreate(instance);
}
@Override
public void onActivityCreated(Bundle instance) {
adapter = new FeedHeadlineAdapter(getActivity(), feedId, selectArticlesForCategory);
getLoaderManager().restartLoader(TYPE_HEADLINE_ID, null, this);
super.onActivityCreated(instance);
// Detect touch gestures like swipe and scroll down:
ActionBar actionBar = ((MenuActivity) getActivity()).getSupportActionBar();
gestureDetector = new GestureDetector(getActivity(),
new HeadlineGestureDetector(actionBar, Controller.getInstance().hideActionbar()));
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event) || v.performClick();
}
};
if (getView() != null) getView().setOnTouchListener(gestureListener);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(FEED_CAT_ID, categoryId);
outState.putInt(FEED_ID, feedId);
outState.putBoolean(FEED_SELECT_ARTICLES, selectArticlesForCategory);
super.onSaveInstanceState(outState);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
// Get selected Article
AdapterView.AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
Article a = (Article) adapter.getItem(info.position);
menu.add(MARK_GROUP, MARK_ABOVE_READ, Menu.NONE, R.string.Commons_MarkAboveRead);
if (a.isUnread) {
menu.add(MARK_GROUP, MARK_READ, Menu.NONE, R.string.Commons_MarkRead);
} else {
menu.add(MARK_GROUP, MARK_READ, Menu.NONE, R.string.Commons_MarkUnread);
}
if (a.isStarred) {
menu.add(MARK_GROUP, MARK_STAR, Menu.NONE, R.string.Commons_MarkUnstar);
} else {
menu.add(MARK_GROUP, MARK_STAR, Menu.NONE, R.string.Commons_MarkStar);
}
if (a.isPublished) {
menu.add(MARK_GROUP, MARK_PUBLISH, Menu.NONE, R.string.Commons_MarkUnpublish);
} else {
menu.add(MARK_GROUP, MARK_PUBLISH, Menu.NONE, R.string.Commons_MarkPublish);
menu.add(MARK_GROUP, MARK_NOTE, Menu.NONE, R.string.Commons_MarkNote);
}
menu.add(MARK_GROUP, SHARE, Menu.NONE, R.string.ArticleActivity_ShareLink);
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
AdapterContextMenuInfo cmi = (AdapterContextMenuInfo) item.getMenuInfo();
if (cmi == null) return false;
Article a = (Article) adapter.getItem(cmi.position);
if (a == null) return false;
switch (item.getItemId()) {
case MARK_READ:
new Updater(getActivity(), new ArticleReadStateUpdater(a, a.isUnread ? 0 : 1))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
case MARK_STAR:
new Updater(getActivity(), new StarredStateUpdater(a, a.isStarred ? 0 : 1))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
case MARK_PUBLISH:
new Updater(getActivity(), new PublishedStateUpdater(a, a.isPublished ? 0 : 1))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
case MARK_NOTE:
new TextInputAlert(this, a).show(getActivity());
break;
case MARK_ABOVE_READ:
new Updater(getActivity(), new ArticleReadStateUpdater(getUnreadAbove(cmi.position), 0))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
case SHARE:
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_TEXT, a.url);
i.putExtra(Intent.EXTRA_SUBJECT, a.title);
startActivity(Intent.createChooser(i, getText(R.string.ArticleActivity_ShareTitle)));
break;
default:
return false;
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true;
switch (item.getItemId()) {
case R.id.Menu_MarkFeedRead: {
boolean backAfterUpdate = Controller.getInstance().goBackAfterMarkAllRead();
if (selectArticlesForCategory) {
IUpdatable updateable = new ReadStateUpdater(categoryId);
ReadStateDialog.getInstance(updateable, backAfterUpdate).show(getFragmentManager());
} else if (feedId >= -4 && feedId < 0) { // Virtual Category
IUpdatable updateable = new ReadStateUpdater(feedId, 42);
ReadStateDialog.getInstance(updateable, backAfterUpdate).show(getFragmentManager());
} else {
new Updater(getActivity(), new ReadStateUpdater(feedId, 42), backAfterUpdate)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
return true;
}
case R.id.Menu_FeedUnsubscribe: {
YesNoUpdaterDialog dialog = YesNoUpdaterDialog
.getInstance(new UnsubscribeUpdater(feedId), R.string.Dialog_unsubscribeTitle,
R.string.Dialog_unsubscribeText);
dialog.show(getFragmentManager(), YesNoUpdaterDialog.DIALOG);
return true;
}
default:
return false;
}
}
/**
* Creates a list of articles which are above the given index in the currently displayed list of items.
*
* @param index the selected index, will be excluded in returned list
* @return a list of items above the selected item
*/
private List<Article> getUnreadAbove(int index) {
List<Article> ret = new ArrayList<>();
for (int i = 0; i < index; i++) {
Article a = (Article) adapter.getItem(i);
if (a != null && a.isUnread) ret.add(a);
}
return ret;
}
public void onAddNoteResult(Article a, String note) {
new Updater(getActivity(), new NoteUpdater(a, note)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public TYPE getType() {
return THIS_TYPE;
}
public int getFeedId() {
return feedId;
}
List<Integer> currentSelectedList = null;
@Override
public void setSelectedId(int selectedId) {
// Cache the current list of articleIds. This is necessary to allow jumping between articles to stop at the
// end of the unread articles. The list in this fragments adapter is reloaded when there are no more unread
// articles but we still want to stop reading when the last unread has been reached.
currentSelectedList = adapter.getIds();
super.setSelectedId(selectedId);
}
public List<Integer> getArticleIds() {
if (currentSelectedList != null) return currentSelectedList;
return adapter.getIds();
}
private class HeadlineGestureDetector extends MyGestureDetector {
private HeadlineGestureDetector(ActionBar actionBar, boolean hideActionbar) {
super(actionBar, hideActionbar);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// Refresh metrics-data in Controller
Controller.refreshDisplayMetrics(
((WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay());
try {
if (Math.abs(e1.getY() - e2.getY()) > Controller.relSwipeMaxOffPath) return false;
if (e1.getX() - e2.getX() > Controller.relSwipeMinDistance
&& Math.abs(velocityX) > Controller.relSwipeThresholdVelocity) {
// right to left swipe
FeedHeadlineActivity activity = (FeedHeadlineActivity) getActivity();
activity.openNextFeed(1);
return true;
} else if (e2.getX() - e1.getX() > Controller.relSwipeMinDistance
&& Math.abs(velocityX) > Controller.relSwipeThresholdVelocity) {
// left to right swipe
FeedHeadlineActivity activity = (FeedHeadlineActivity) getActivity();
activity.openNextFeed(-1);
return true;
}
} catch (Exception e) {
// Empty!
}
return false;
}
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == TYPE_HEADLINE_ID) {
Builder builder = ListContentProvider.CONTENT_URI_HEAD.buildUpon();
builder.appendQueryParameter(ListContentProvider.PARAM_CAT_ID, categoryId + "");
builder.appendQueryParameter(ListContentProvider.PARAM_FEED_ID, feedId + "");
builder.appendQueryParameter(ListContentProvider.PARAM_SELECT_FOR_CAT,
(selectArticlesForCategory ? "1" : "0"));
headlineUri = builder.build();
return new CursorLoader(getActivity(), headlineUri, null, null, null, null);
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (loader.getId() == TYPE_HEADLINE_ID) adapter.changeCursor(data);
super.onLoadFinished(loader, data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (loader.getId() == TYPE_HEADLINE_ID) adapter.changeCursor(null);
}
@Override
protected void fetchOtherData() {
if (selectArticlesForCategory) {
Category category = DBHelper.getInstance().getCategory(categoryId);
if (category != null) title = category.title;
} else if (feedId >= -4 && feedId < 0) { // Virtual Category
Category category = DBHelper.getInstance().getCategory(feedId);
if (category != null) title = category.title;
} else {
Feed feed = DBHelper.getInstance().getFeed(feedId);
if (feed != null) title = feed.title;
}
unreadCount = DBHelper.getInstance()
.getUnreadCount(selectArticlesForCategory ? categoryId : feedId, selectArticlesForCategory);
}
@Override
public void doRefresh() {
Activity activity = getActivity();
if (activity != null && headlineUri != null) activity.getContentResolver().notifyChange(headlineUri, null);
super.doRefresh();
}
}