/*******************************************************************************
* This file is part of RedReader.
*
* RedReader 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.
*
* RedReader 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 RedReader. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package org.quantumbadger.redreader.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.*;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.quantumbadger.redreader.R;
import org.quantumbadger.redreader.account.RedditAccount;
import org.quantumbadger.redreader.account.RedditAccountManager;
import org.quantumbadger.redreader.activities.BugReportActivity;
import org.quantumbadger.redreader.activities.CommentReplyActivity;
import org.quantumbadger.redreader.activities.OptionsMenuUtility;
import org.quantumbadger.redreader.adapters.FilteredCommentListingManager;
import org.quantumbadger.redreader.adapters.GroupedRecyclerViewAdapter;
import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategy;
import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyAlways;
import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyIfNotCached;
import org.quantumbadger.redreader.common.*;
import org.quantumbadger.redreader.reddit.CommentListingRequest;
import org.quantumbadger.redreader.reddit.RedditCommentListItem;
import org.quantumbadger.redreader.reddit.api.RedditAPICommentAction;
import org.quantumbadger.redreader.reddit.prepared.RedditChangeDataManager;
import org.quantumbadger.redreader.reddit.prepared.RedditPreparedPost;
import org.quantumbadger.redreader.reddit.prepared.RedditRenderableComment;
import org.quantumbadger.redreader.reddit.url.RedditURLParser;
import org.quantumbadger.redreader.views.RedditCommentView;
import org.quantumbadger.redreader.views.RedditPostHeaderView;
import org.quantumbadger.redreader.views.RedditPostView;
import org.quantumbadger.redreader.views.ScrollbarRecyclerViewManager;
import org.quantumbadger.redreader.views.bezelmenu.BezelSwipeOverlay;
import org.quantumbadger.redreader.views.bezelmenu.SideToolbarOverlay;
import org.quantumbadger.redreader.views.liststatus.CommentSubThreadView;
import org.quantumbadger.redreader.views.liststatus.ErrorView;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.UUID;
public class CommentListingFragment extends RRFragment
implements RedditPostView.PostSelectionListener,
RedditCommentView.CommentListener,
CommentListingRequest.Listener {
private static final String SAVEDSTATE_FIRST_VISIBLE_POS = "firstVisiblePosition";
private final RedditAccount mUser;
private final ArrayList<RedditURLParser.RedditURL> mAllUrls;
private final LinkedList<RedditURLParser.RedditURL> mUrlsToDownload;
private final UUID mSession;
private final DownloadStrategy mDownloadStrategy;
private RedditPreparedPost mPost = null;
private boolean isArchived;
private final FilteredCommentListingManager mCommentListingManager;
private final RecyclerView mRecyclerView;
private final FrameLayout mOuterFrame;
private final @Nullable LinearLayout mFloatingToolbar;
private final float mCommentFontScale;
private final boolean mShowLinkButtons;
private Long mCachedTimestamp = null;
private Integer mPreviousFirstVisibleItemPosition;
public CommentListingFragment(
final AppCompatActivity parent,
final Bundle savedInstanceState,
final ArrayList<RedditURLParser.RedditURL> urls,
final UUID session,
final String searchString,
final boolean forceDownload) {
super(parent, savedInstanceState);
if(savedInstanceState != null) {
mPreviousFirstVisibleItemPosition = savedInstanceState.getInt(SAVEDSTATE_FIRST_VISIBLE_POS);
}
mCommentListingManager = new FilteredCommentListingManager(parent, searchString);
mAllUrls = urls;
mUrlsToDownload = new LinkedList<>(mAllUrls);
this.mSession = session;
if(forceDownload) {
mDownloadStrategy = DownloadStrategyAlways.INSTANCE;
} else {
mDownloadStrategy = DownloadStrategyIfNotCached.INSTANCE;
}
mUser = RedditAccountManager.getInstance(getActivity()).getDefaultAccount();
parent.invalidateOptionsMenu();
final Context context = getActivity();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
mCommentFontScale = PrefsUtility.appearance_fontscale_comments(context, prefs);
mShowLinkButtons = PrefsUtility.pref_appearance_linkbuttons(context, prefs);
mOuterFrame = new FrameLayout(context);
final ScrollbarRecyclerViewManager recyclerViewManager
= new ScrollbarRecyclerViewManager(context, null, false);
if(parent instanceof OptionsMenuUtility.OptionsMenuCommentsListener
&& PrefsUtility.pref_behaviour_enable_swipe_refresh(context, prefs)) {
recyclerViewManager.enablePullToRefresh(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
((OptionsMenuUtility.OptionsMenuCommentsListener)parent).onRefreshComments();
}
});
}
mRecyclerView = recyclerViewManager.getRecyclerView();
mCommentListingManager.setLayoutManager((LinearLayoutManager) mRecyclerView.getLayoutManager());
mRecyclerView.setAdapter(mCommentListingManager.getAdapter());
mOuterFrame.addView(recyclerViewManager.getOuterView());
mRecyclerView.setItemAnimator(null);
/* TODO
{
final RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
itemAnimator.setRemoveDuration(80);
itemAnimator.setChangeDuration(80);
itemAnimator.setAddDuration(80);
itemAnimator.setMoveDuration(80);
}
*/
if(!PrefsUtility.pref_appearance_comments_show_floating_toolbar(context, prefs)) {
mFloatingToolbar = null;
} else {
mFloatingToolbar = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.floating_toolbar, mOuterFrame, false);
// We need a container so that setVisible() doesn't mess with the Z-order
final FrameLayout floatingToolbarContainer = new FrameLayout(context);
floatingToolbarContainer.addView(mFloatingToolbar);
mOuterFrame.addView(floatingToolbarContainer);
if(PrefsUtility.isNightMode(context)) {
mFloatingToolbar.setBackgroundColor(Color.argb(0xCC, 0x33, 0x33, 0x33));
}
final int buttonVPadding = General.dpToPixels(context, 12);
final int buttonHPadding = General.dpToPixels(context, 16);
{
final ImageButton previousButton = (ImageButton) LayoutInflater.from(context).inflate(
R.layout.flat_image_button, mFloatingToolbar, false);
previousButton.setPadding(buttonHPadding, buttonVPadding, buttonHPadding, buttonVPadding);
previousButton.setImageResource(R.drawable.ic_ff_up_dark);
previousButton.setContentDescription(getString(R.string.button_prev_comment_parent));
mFloatingToolbar.addView(previousButton);
previousButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
for(int pos = layoutManager.findFirstVisibleItemPosition() - 1;
pos > 0;
pos--) {
final GroupedRecyclerViewAdapter.Item item = mCommentListingManager.getItemAtPosition(pos);
if(item instanceof RedditCommentListItem
&& ((RedditCommentListItem) item).isComment()
&& ((RedditCommentListItem) item).getIndent() == 0) {
layoutManager.scrollToPositionWithOffset(pos, 0);
return;
}
}
layoutManager.scrollToPositionWithOffset(0, 0);
}
});
previousButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(final View view) {
General.quickToast(context, R.string.button_prev_comment_parent);
return true;
}
});
}
{
final ImageButton nextButton = (ImageButton) LayoutInflater.from(context).inflate(
R.layout.flat_image_button, mFloatingToolbar, false);
nextButton.setPadding(buttonHPadding, buttonVPadding, buttonHPadding, buttonVPadding);
nextButton.setImageResource(R.drawable.ic_ff_down_dark);
nextButton.setContentDescription(getString(R.string.button_next_comment_parent));
mFloatingToolbar.addView(nextButton);
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
for(int pos = layoutManager.findFirstVisibleItemPosition() + 1;
pos < layoutManager.getItemCount();
pos++) {
final GroupedRecyclerViewAdapter.Item item = mCommentListingManager.getItemAtPosition(pos);
if(item instanceof RedditCommentListItem
&& ((RedditCommentListItem) item).isComment()
&& ((RedditCommentListItem) item).getIndent() == 0) {
layoutManager.scrollToPositionWithOffset(pos, 0);
break;
}
}
}
});
nextButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(final View view) {
General.quickToast(context, R.string.button_next_comment_parent);
return true;
}
});
}
}
final SideToolbarOverlay toolbarOverlay = new SideToolbarOverlay(context);
final BezelSwipeOverlay bezelOverlay = new BezelSwipeOverlay(context, new BezelSwipeOverlay.BezelSwipeListener() {
@Override
public boolean onSwipe(@BezelSwipeOverlay.SwipeEdge int edge) {
if(mPost == null) return false;
toolbarOverlay.setContents(mPost.generateToolbar(getActivity(), true, toolbarOverlay));
toolbarOverlay.show(edge == BezelSwipeOverlay.LEFT ?
SideToolbarOverlay.SideToolbarPosition.LEFT : SideToolbarOverlay.SideToolbarPosition.RIGHT);
return true;
}
public boolean onTap() {
if(toolbarOverlay.isShown()) {
toolbarOverlay.hide();
return true;
}
return false;
}
});
mOuterFrame.addView(bezelOverlay);
mOuterFrame.addView(toolbarOverlay);
bezelOverlay.getLayoutParams().width = android.widget.FrameLayout.LayoutParams.MATCH_PARENT;
bezelOverlay.getLayoutParams().height = android.widget.FrameLayout.LayoutParams.MATCH_PARENT;
toolbarOverlay.getLayoutParams().width = android.widget.FrameLayout.LayoutParams.MATCH_PARENT;
toolbarOverlay.getLayoutParams().height = android.widget.FrameLayout.LayoutParams.MATCH_PARENT;
makeNextRequest(context);
}
public void handleCommentVisibilityToggle(final RedditCommentView view) {
final RedditChangeDataManager changeDataManager = RedditChangeDataManager.getInstance(mUser);
final RedditCommentListItem item = view.getComment();
if(item.isComment()) {
final RedditRenderableComment comment = item.asComment();
changeDataManager.markHidden(
RRTime.utcCurrentTimeMillis(),
comment,
!comment.isCollapsed(changeDataManager));
mCommentListingManager.updateHiddenStatus();
final LinearLayoutManager layoutManager = (LinearLayoutManager)mRecyclerView.getLayoutManager();
final int position = layoutManager.getPosition(view);
if(position == layoutManager.findFirstVisibleItemPosition()) {
layoutManager.scrollToPositionWithOffset(position, 0);
}
}
}
@Override
public View getView() {
return mOuterFrame;
}
@Override
public Bundle onSaveInstanceState() {
final Bundle bundle = new Bundle();
final LinearLayoutManager layoutManager = (LinearLayoutManager)mRecyclerView.getLayoutManager();
bundle.putInt(SAVEDSTATE_FIRST_VISIBLE_POS, layoutManager.findFirstVisibleItemPosition());
return bundle;
}
@SuppressLint("WrongConstant")
private void makeNextRequest(final Context context) {
if(!mUrlsToDownload.isEmpty()) {
new CommentListingRequest(
context,
this,
getActivity(),
mUrlsToDownload.getFirst(),
mAllUrls.size() == 1,
mUrlsToDownload.getFirst(),
mUser,
mSession,
mDownloadStrategy,
this
);
}
}
@Override
public void onCommentClicked(final RedditCommentView view) {
switch(PrefsUtility.pref_behaviour_actions_comment_tap(
getActivity(),
PreferenceManager.getDefaultSharedPreferences(getActivity()))) {
case COLLAPSE:
handleCommentVisibilityToggle(view);
break;
case ACTION_MENU: {
final RedditCommentListItem item = view.getComment();
if(item != null && item.isComment()) {
RedditAPICommentAction.showActionMenu(
getActivity(),
this,
item.asComment(),
view,
RedditChangeDataManager.getInstance(mUser),
isArchived);
}
break;
}
}
}
@Override
public void onCommentLongClicked(final RedditCommentView view) {
switch(PrefsUtility.pref_behaviour_actions_comment_longclick(
getActivity(),
PreferenceManager.getDefaultSharedPreferences(getActivity()))) {
case ACTION_MENU:{
final RedditCommentListItem item = view.getComment();
if(item != null && item.isComment()) {
RedditAPICommentAction.showActionMenu(
getActivity(),
this,
item.asComment(),
view,
RedditChangeDataManager.getInstance(mUser),
isArchived);
}
break;
}
case COLLAPSE:
handleCommentVisibilityToggle(view);
break;
case NOTHING:
break;
}
}
@Override
public void onCommentListingRequestDownloadNecessary() {
mCommentListingManager.setLoadingVisible(true);
}
@Override
public void onCommentListingRequestDownloadStarted() {}
@Override
public void onCommentListingRequestException(final Throwable t) {
BugReportActivity.handleGlobalError(getActivity(), t);
}
@Override
public void onCommentListingRequestFailure(final RRError error) {
mCommentListingManager.setLoadingVisible(false);
mCommentListingManager.addFooterError(new ErrorView(getActivity(), error));
}
@Override
public void onCommentListingRequestCachedCopy(final long timestamp) {
mCachedTimestamp = timestamp;
}
@Override
public void onCommentListingRequestParseStart() {
mCommentListingManager.setLoadingVisible(true);
}
@Override
public void onCommentListingRequestAuthorizing() {
mCommentListingManager.setLoadingVisible(true);
}
@Override
public void onCommentListingRequestPostDownloaded(final RedditPreparedPost post) {
final Context context = getActivity();
if(mPost == null) {
final RRThemeAttributes attr = new RRThemeAttributes(context);
mPost = post;
isArchived = post.isArchived;
final RedditPostHeaderView postHeader = new RedditPostHeaderView(
getActivity(),
this.mPost);
mCommentListingManager.addPostHeader(postHeader);
((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(0, 0);
if(post.src.getSelfText() != null) {
final ViewGroup selfText = post.src.getSelfText().buildView(
getActivity(), attr.rrMainTextCol, 14f * mCommentFontScale, mShowLinkButtons);
selfText.setFocusable(false);
selfText.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
final int paddingPx = General.dpToPixels(context, 10);
final FrameLayout paddingLayout = new FrameLayout(context);
final TextView collapsedView = new TextView(context);
collapsedView.setText("[ + ] " + getActivity().getString(R.string.collapsed_self_post));
collapsedView.setVisibility(View.GONE);
collapsedView.setPadding(paddingPx, paddingPx, paddingPx, paddingPx);
paddingLayout.addView(selfText);
paddingLayout.addView(collapsedView);
paddingLayout.setPadding(paddingPx, paddingPx, paddingPx, paddingPx);
paddingLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (selfText.getVisibility() == View.GONE){
selfText.setVisibility(View.VISIBLE);
collapsedView.setVisibility(View.GONE);
} else {
selfText.setVisibility(View.GONE);
collapsedView.setVisibility(View.VISIBLE);
}
}
});
// TODO mListHeaderNotifications.setBackgroundColor(Color.argb(35, 128, 128, 128));
mCommentListingManager.addPostSelfText(paddingLayout);
}
if(!General.isTablet(context, PreferenceManager.getDefaultSharedPreferences(context))) {
getActivity().setTitle(post.src.getTitle());
}
if (mCommentListingManager.isSearchListing()) {
final CommentSubThreadView searchCommentThreadView= new CommentSubThreadView(
getActivity(),
mAllUrls.get(0).asPostCommentListURL(),
R.string.comment_header_search_thread_title
);
mCommentListingManager.addNotification(searchCommentThreadView);
} else if(!mAllUrls.isEmpty()
&& mAllUrls.get(0).pathType() == RedditURLParser.POST_COMMENT_LISTING_URL
&& mAllUrls.get(0).asPostCommentListURL().commentId != null) {
final CommentSubThreadView specificCommentThreadView = new CommentSubThreadView(
getActivity(),
mAllUrls.get(0).asPostCommentListURL(),
R.string.comment_header_specific_thread_title);
mCommentListingManager.addNotification(specificCommentThreadView);
}
// TODO pref (currently 10 mins)
if(mCachedTimestamp != null && RRTime.since(mCachedTimestamp) > 10 * 60 * 1000) {
final TextView cacheNotif = (TextView) LayoutInflater.from(getActivity())
.inflate(R.layout.cached_header, null, false);
cacheNotif.setText(getActivity().getString(R.string.listing_cached,
RRTime.formatDateTime(mCachedTimestamp, getActivity())));
mCommentListingManager.addNotification(cacheNotif);
}
}
}
@Override
public void onCommentListingRequestAllItemsDownloaded(final ArrayList<RedditCommentListItem> items) {
mCommentListingManager.addComments(items);
if(mFloatingToolbar != null) {
mFloatingToolbar.setVisibility(View.VISIBLE);
final Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_in_from_bottom);
animation.setInterpolator(new OvershootInterpolator());
mFloatingToolbar.startAnimation(animation);
}
mUrlsToDownload.removeFirst();
final LinearLayoutManager layoutManager = (LinearLayoutManager)mRecyclerView.getLayoutManager();
if(mPreviousFirstVisibleItemPosition != null
&& layoutManager.getItemCount() > mPreviousFirstVisibleItemPosition) {
layoutManager.scrollToPositionWithOffset(
mPreviousFirstVisibleItemPosition,
0);
mPreviousFirstVisibleItemPosition = null;
}
if(mUrlsToDownload.isEmpty()) {
if(mCommentListingManager.getCommentCount() == 0) {
final View emptyView = LayoutInflater.from(getContext()).inflate(
R.layout.no_comments_yet,
mRecyclerView,
false);
if (mCommentListingManager.isSearchListing()) {
((TextView) emptyView.findViewById(R.id.empty_view_text)).setText(R.string.no_search_results);
}
mCommentListingManager.addViewToItems(emptyView);
} else {
final View blankView = new View(getContext());
blankView.setMinimumWidth(1);
blankView.setMinimumHeight(General.dpToPixels(getContext(), 96));
mCommentListingManager.addViewToItems(blankView);
}
mCommentListingManager.setLoadingVisible(false);
} else {
makeNextRequest(getActivity());
}
}
@Override
public void onCreateOptionsMenu(Menu menu) {
if(mAllUrls != null && mAllUrls.size() > 0 && mAllUrls.get(0).pathType() == RedditURLParser.POST_COMMENT_LISTING_URL) {
menu.add(R.string.action_reply);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getTitle() != null
&& item.getTitle().equals(getActivity().getString(R.string.action_reply))) {
onParentReply();
return true;
}
return false;
}
private void onParentReply() {
if(mPost != null) {
final Intent intent = new Intent(getActivity(), CommentReplyActivity.class);
intent.putExtra(CommentReplyActivity.PARENT_ID_AND_TYPE_KEY, mPost.src.getIdAndType());
intent.putExtra(CommentReplyActivity.PARENT_MARKDOWN_KEY, mPost.src.getUnescapedSelfText());
startActivity(intent);
} else {
General.quickToast(getActivity(), R.string.error_toast_parent_post_not_downloaded);
}
}
public void onPostSelected(final RedditPreparedPost post) {
((RedditPostView.PostSelectionListener)getActivity()).onPostSelected(post);
}
public void onPostCommentsSelected(final RedditPreparedPost post) {
((RedditPostView.PostSelectionListener)getActivity()).onPostCommentsSelected(post);
}
}