/**
* Android ownCloud News
*
* @author David Luhmer
* @copyright 2013 David Luhmer david-dev@live.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.luhmer.owncloudnewsreader;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import org.apache.commons.lang3.time.StopWatch;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter;
import de.luhmer.owncloudnewsreader.adapter.DividerItemDecoration;
import de.luhmer.owncloudnewsreader.adapter.NewsListRecyclerAdapter;
import de.luhmer.owncloudnewsreader.adapter.ViewHolder;
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm.SORT_DIRECTION;
import de.luhmer.owncloudnewsreader.database.model.RssItem;
import de.luhmer.owncloudnewsreader.database.model.RssItemDao;
import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper;
/**
* A fragment representing a single NewsReader detail screen. This fragment is
* either contained in a {@link NewsReaderListActivity} in two-pane mode (on
* tablets) or a {@link NewsReaderListActivity} on handsets.
*/
public class NewsReaderDetailFragment extends Fragment {
protected final String TAG = getClass().getCanonicalName();
private Long idFeed;
private Drawable markAsReadDrawable;
private Drawable starredDrawable;
private int accentColor;
private Parcelable layoutManagerSavedState;
/**
* @return the idFeed
*/
public Long getIdFeed() {
return idFeed;
}
Long idFolder;
/**
* @return the idFolder
*/
public Long getIdFolder() {
return idFolder;
}
String titel;
/**
* @return the titel
*/
public String getTitel() {
return titel;
}
private int onResumeCount = 0;
private static final String LAYOUT_MANAGER_STATE = "LAYOUT_MANAGER_STATE";
private boolean mMarkAsReadWhileScrollingEnabled;
@Bind(R.id.pb_loading) ProgressBar pbLoading;
@Bind(R.id.tv_no_items_available) View tvNoItemsAvailable;
@Bind(R.id.list) RecyclerView recyclerView;
@Bind(R.id.swipeRefresh) SwipeRefreshLayout swipeRefresh;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public NewsReaderDetailFragment() {
}
public void setData(Long idFeed, Long idFolder, String titel, boolean updateListView) {
Log.v(TAG, "Creating new itstance");
this.idFeed = idFeed;
this.idFolder = idFolder;
this.titel = titel;
((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(titel);
if(updateListView)
UpdateCurrentRssView(getActivity());
else
RefreshCurrentRssView();
}
@Override
public void onResume() {
Log.v(TAG, "onResume called!");
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mMarkAsReadWhileScrollingEnabled = mPrefs.getBoolean(SettingsActivity.CB_MARK_AS_READ_WHILE_SCROLLING_STRING, false);
//When the fragment is instantiated by the xml file, onResume will be called twice
if(onResumeCount >= 2) {
RefreshCurrentRssView();
}
onResumeCount++;
super.onResume();
}
private void handleMarkAsReadScrollEvent() {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
int visibleItemCount = lastVisibleItem - firstVisibleItem;
int totalItemCount = recyclerView.getAdapter().getItemCount();
NewsListRecyclerAdapter adapter = (NewsListRecyclerAdapter) recyclerView.getAdapter();
//Set the item at top to read
ViewHolder vh = (ViewHolder) recyclerView.findViewHolderForLayoutPosition(firstVisibleItem);
if (vh != null && !vh.shouldStayUnread()) {
adapter.ChangeReadStateOfItem(vh, true);
}
//Check if Listview is scrolled to bottom
if (lastVisibleItem == (totalItemCount-1) &&
visibleItemCount != 0 && //Check if list is empty
recyclerView.getChildAt(visibleItemCount).getBottom() <= recyclerView.getHeight()) {
for (int i = firstVisibleItem; i <= lastVisibleItem; i++) {
RecyclerView.ViewHolder vhTemp = recyclerView.findViewHolderForLayoutPosition(i);
if(vhTemp instanceof ViewHolder) { //Check for ViewHolder instance because of ProgressViewHolder
vh = (ViewHolder) vhTemp;
if (vh != null && !vh.shouldStayUnread()) {
adapter.ChangeReadStateOfItem(vh, true);
}
}
}
}
}
public void UpdateMenuItemsState()
{
NewsReaderListActivity nla = (NewsReaderListActivity)getActivity();
if(nla.getMenuItemDownloadMoreItems() != null)
{
if(idFolder != null && idFolder == SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS.getValue()) {
nla.getMenuItemDownloadMoreItems().setEnabled(false);
} else {
nla.getMenuItemDownloadMoreItems().setEnabled(true);
}
}
}
public void notifyDataSetChangedOnAdapter()
{
NewsListRecyclerAdapter nca = (NewsListRecyclerAdapter) recyclerView.getAdapter();
if(nca != null)
nca.notifyDataSetChanged();
}
/**
* Refreshes the current RSS-View
*/
public void RefreshCurrentRssView() {
Log.v(TAG, "RefreshCurrentRssView");
NewsListRecyclerAdapter nra = ((NewsListRecyclerAdapter) recyclerView.getAdapter());
if(nra != null) {
nra.refreshAdapterDataAsync(new NewsListRecyclerAdapter.IOnRefreshFinished() {
@Override
public void OnRefreshFinished() {
pbLoading.setVisibility(View.GONE);
if (layoutManagerSavedState != null) {
recyclerView.getLayoutManager().onRestoreInstanceState(layoutManagerSavedState);
layoutManagerSavedState = null;
}
}
});
}
}
/**
* Updates the current RSS-View
* @param context
*/
public void UpdateCurrentRssView(Context context) {
Log.v(TAG, "UpdateCurrentRssView");
AsyncTaskHelper.StartAsyncTask(new UpdateCurrentRssViewTask(context));
}
public RecyclerView getRecyclerView() {
return recyclerView;
}
public LinearLayoutManager getLayoutManager() {
if(recyclerView == null) return null;
return (LinearLayoutManager) recyclerView.getLayoutManager();
}
private class UpdateCurrentRssViewTask extends AsyncTask<Void, Void, List<RssItem>> {
private Context context;
private SORT_DIRECTION sortDirection;
public UpdateCurrentRssViewTask(Context context) {
this.context = context;
}
@Override
protected void onPreExecute() {
pbLoading.setVisibility(View.VISIBLE);
tvNoItemsAvailable.setVisibility(View.GONE);
sortDirection = getSortDirection(context);
super.onPreExecute();
}
@Override
protected List<RssItem> doInBackground(Void... urls) {
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(context);
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean onlyUnreadItems = mPrefs.getBoolean(SettingsActivity.CB_SHOWONLYUNREAD_STRING, false);
boolean onlyStarredItems = false;
if (idFolder != null)
if (idFolder == SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS.getValue())
onlyStarredItems = true;
String sqlSelectStatement = null;
if (idFeed != null)
sqlSelectStatement = dbConn.getAllItemsIdsForFeedSQL(idFeed, onlyUnreadItems, onlyStarredItems, sortDirection);
else if (idFolder != null) {
if (idFolder == SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS.getValue())
onlyUnreadItems = false;
sqlSelectStatement = dbConn.getAllItemsIdsForFolderSQL(idFolder, onlyUnreadItems, sortDirection);
}
if (sqlSelectStatement != null) {
int index = sqlSelectStatement.indexOf("ORDER BY");
if(index == -1) {
index = sqlSelectStatement.length();
}
sqlSelectStatement = new StringBuilder(sqlSelectStatement).insert(index, " GROUP BY " + RssItemDao.Properties.Fingerprint.columnName + " ").toString();
dbConn.insertIntoRssCurrentViewTable(sqlSelectStatement);
}
StopWatch sw = new StopWatch();
sw.start();
List<RssItem> items = dbConn.getCurrentRssItemView(0);
sw.stop();
Log.v(TAG, "Time needed (init loading): " + sw.toString());
return items;
}
@Override
protected void onPostExecute(List<RssItem> rssItem) {
try
{
NewsListRecyclerAdapter nra = ((NewsListRecyclerAdapter) recyclerView.getAdapter());
if(nra == null) {
nra = new NewsListRecyclerAdapter(getActivity(), recyclerView, (PodcastFragmentActivity) getActivity());
recyclerView.setAdapter(nra);
}
nra.updateAdapterData(rssItem);
pbLoading.setVisibility(View.GONE);
if(nra.getItemCount() <= 0) {
tvNoItemsAvailable.setVisibility(View.VISIBLE);
} else {
tvNoItemsAvailable.setVisibility(View.GONE);
}
recyclerView.scrollToPosition(0);
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}
public static SORT_DIRECTION getSortDirection(Context context) {
return NewsDetailActivity.getSortDirectionFromSettings(context);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_newsreader_detail, container, false);
ButterKnife.bind(this, rootView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator(new DefaultItemAnimator());
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new NewsReaderItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity()));
swipeRefresh.setColorSchemeColors(accentColor);
swipeRefresh.setOnRefreshListener((SwipeRefreshLayout.OnRefreshListener) getActivity());
recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
GestureDetectorCompat detector = new GestureDetectorCompat(getActivity(), new RecyclerViewOnGestureListener());
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
detector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
return rootView;
}
private class RecyclerViewOnGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(mMarkAsReadWhileScrollingEnabled && (e2.getY() - e1.getY()) < 0) { // (distance < 0) => scroll up
handleMarkAsReadScrollEvent();
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
}
@Override
public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
TypedArray a = context.obtainStyledAttributes(attrs,new int[]{R.attr.markasreadDrawable, R.attr.starredDrawable, R.attr.colorAccent});
markAsReadDrawable = a.getDrawable(0);
starredDrawable = a.getDrawable(1);
int color = Constants.IsNextCloud(getContext()) ? R.color.nextcloudBlueLight : R.color.owncloudBlueLight;
accentColor = a.getColor(2, ContextCompat.getColor(context, color));
a.recycle();
}
// TODO: somehow always cancel item out animation
private class NewsReaderItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
public NewsReaderItemTouchHelperCallback() {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return 0.25f;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) {
final NewsListRecyclerAdapter adapter = (NewsListRecyclerAdapter) recyclerView.getAdapter();
if(direction == ItemTouchHelper.LEFT) {
adapter.toggleReadStateOfItem((ViewHolder) viewHolder);
} else if(direction == ItemTouchHelper.RIGHT) {
adapter.toggleStarredStateOfItem((ViewHolder) viewHolder);
//adapter.toggleReadStateOfItem((ViewHolder) viewHolder);
}
// Hack to reset view, see https://code.google.com/p/android/issues/detail?id=175798
recyclerView.removeView(viewHolder.itemView);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
// swipeRefresh cancels swiping left/right when accidentally moving in the y direction;
swipeRefresh.setEnabled(!isCurrentlyActive);
if(isCurrentlyActive) {
Rect viewRect = new Rect();
viewHolder.itemView.getDrawingRect(viewRect);
float fractionMoved = Math.abs(dX/viewHolder.itemView.getMeasuredWidth());
Drawable drawable;
if(dX < 0) {
drawable = markAsReadDrawable;
viewRect.left = (int) dX + viewRect.right;
} else {
drawable = starredDrawable;
viewRect.right = (int) dX - viewRect.left;
}
if(fractionMoved > getSwipeThreshold(viewHolder))
drawable.setState(new int[]{android.R.attr.state_above_anchor});
else
drawable.setState(new int[]{-android.R.attr.state_above_anchor});
viewRect.offset(0,viewHolder.itemView.getTop());
drawable.setBounds(viewRect);
drawable.draw(c);
}
}
}
@Override
public void onViewStateRestored(Bundle savedInstanceState) {
if(savedInstanceState != null)
layoutManagerSavedState = savedInstanceState.getParcelable(LAYOUT_MANAGER_STATE);
super.onViewStateRestored(savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(LAYOUT_MANAGER_STATE, getLayoutManager().onSaveInstanceState());
}
public int getFirstVisibleScrollPosition() {
LinearLayoutManager layoutManager = ((LinearLayoutManager)recyclerView.getLayoutManager());
return layoutManager.findFirstVisibleItemPosition();
}
}