blob: e650bc22bc9a7181ba01341ec50c453afb09048d [file] [log] [blame]
package org.wordpress.android.ui.reader.adapters;
import android.content.Context;
import android.os.AsyncTask;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.analytics.AnalyticsTracker;
import org.wordpress.android.datasets.ReaderPostTable;
import org.wordpress.android.models.ReaderPost;
import org.wordpress.android.models.ReaderPostDiscoverData;
import org.wordpress.android.models.ReaderPostList;
import org.wordpress.android.models.ReaderTag;
import org.wordpress.android.ui.reader.ReaderActivityLauncher;
import org.wordpress.android.ui.reader.ReaderAnim;
import org.wordpress.android.ui.reader.ReaderConstants;
import org.wordpress.android.ui.reader.ReaderInterfaces;
import org.wordpress.android.ui.reader.ReaderTypes;
import org.wordpress.android.ui.reader.actions.ReaderActions;
import org.wordpress.android.ui.reader.actions.ReaderBlogActions;
import org.wordpress.android.ui.reader.actions.ReaderPostActions;
import org.wordpress.android.ui.reader.models.ReaderBlogIdPostId;
import org.wordpress.android.ui.reader.utils.ReaderUtils;
import org.wordpress.android.ui.reader.utils.ReaderXPostUtils;
import org.wordpress.android.ui.reader.views.ReaderFollowButton;
import org.wordpress.android.ui.reader.views.ReaderGapMarkerView;
import org.wordpress.android.ui.reader.views.ReaderIconCountView;
import org.wordpress.android.ui.reader.views.ReaderSiteHeaderView;
import org.wordpress.android.ui.reader.views.ReaderTagHeaderView;
import org.wordpress.android.ui.reader.views.ReaderThumbnailStrip;
import org.wordpress.android.util.AnalyticsUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.DateTimeUtils;
import org.wordpress.android.util.DisplayUtils;
import org.wordpress.android.util.GravatarUtils;
import org.wordpress.android.util.NetworkUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.util.UrlUtils;
import org.wordpress.android.widgets.WPNetworkImageView;
import java.util.HashSet;
public class ReaderPostAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private ReaderTag mCurrentTag;
private long mCurrentBlogId;
private long mCurrentFeedId;
private int mGapMarkerPosition = -1;
private final int mPhotonWidth;
private final int mPhotonHeight;
private final int mAvatarSzMedium;
private final int mAvatarSzSmall;
private final int mAvatarSzTiny;
private final int mMarginLarge;
private boolean mCanRequestMorePosts;
private final boolean mIsLoggedOutReader;
private final ReaderTypes.ReaderPostListType mPostListType;
private final ReaderPostList mPosts = new ReaderPostList();
private final HashSet<String> mRenderedIds = new HashSet<>();
private ReaderInterfaces.OnPostSelectedListener mPostSelectedListener;
private ReaderInterfaces.OnTagSelectedListener mOnTagSelectedListener;
private ReaderInterfaces.OnPostPopupListener mOnPostPopupListener;
private ReaderInterfaces.DataLoadedListener mDataLoadedListener;
private ReaderActions.DataRequestedListener mDataRequestedListener;
private ReaderSiteHeaderView.OnBlogInfoLoadedListener mBlogInfoLoadedListener;
// the large "tbl_posts.text" column is unused here, so skip it when querying
private static final boolean EXCLUDE_TEXT_COLUMN = true;
private static final int MAX_ROWS = ReaderConstants.READER_MAX_POSTS_TO_DISPLAY;
private static final int VIEW_TYPE_POST = 0;
private static final int VIEW_TYPE_XPOST = 1;
private static final int VIEW_TYPE_SITE_HEADER = 2;
private static final int VIEW_TYPE_TAG_HEADER = 3;
private static final int VIEW_TYPE_GAP_MARKER = 4;
private static final long ITEM_ID_CUSTOM_VIEW = -1L;
/*
* cross-post
*/
class ReaderXPostViewHolder extends RecyclerView.ViewHolder {
private final CardView cardView;
private final WPNetworkImageView imgAvatar;
private final WPNetworkImageView imgBlavatar;
private final TextView txtTitle;
private final TextView txtSubtitle;
public ReaderXPostViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.card_view);
imgAvatar = (WPNetworkImageView) itemView.findViewById(R.id.image_avatar);
imgBlavatar = (WPNetworkImageView) itemView.findViewById(R.id.image_blavatar);
txtTitle = (TextView) itemView.findViewById(R.id.text_title);
txtSubtitle = (TextView) itemView.findViewById(R.id.text_subtitle);
}
}
/*
* full post
*/
class ReaderPostViewHolder extends RecyclerView.ViewHolder {
private final CardView cardView;
private final TextView txtTitle;
private final TextView txtText;
private final TextView txtBlogName;
private final TextView txtDomain;
private final TextView txtDateline;
private final TextView txtTag;
private final ReaderIconCountView commentCount;
private final ReaderIconCountView likeCount;
private final ImageView imgMore;
private final WPNetworkImageView imgFeatured;
private final WPNetworkImageView imgAvatar;
private final WPNetworkImageView imgBlavatar;
private final ViewGroup layoutPostHeader;
private final ReaderFollowButton followButton;
private final ViewGroup layoutDiscover;
private final WPNetworkImageView imgDiscoverAvatar;
private final TextView txtDiscover;
private final ReaderThumbnailStrip thumbnailStrip;
public ReaderPostViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView.findViewById(R.id.card_view);
txtTitle = (TextView) itemView.findViewById(R.id.text_title);
txtText = (TextView) itemView.findViewById(R.id.text_excerpt);
txtBlogName = (TextView) itemView.findViewById(R.id.text_blog_name);
txtDomain = (TextView) itemView.findViewById(R.id.text_domain);
txtDateline = (TextView) itemView.findViewById(R.id.text_dateline);
txtTag = (TextView) itemView.findViewById(R.id.text_tag);
commentCount = (ReaderIconCountView) itemView.findViewById(R.id.count_comments);
likeCount = (ReaderIconCountView) itemView.findViewById(R.id.count_likes);
imgFeatured = (WPNetworkImageView) itemView.findViewById(R.id.image_featured);
imgBlavatar = (WPNetworkImageView) itemView.findViewById(R.id.image_blavatar);
imgAvatar = (WPNetworkImageView) itemView.findViewById(R.id.image_avatar);
imgMore = (ImageView) itemView.findViewById(R.id.image_more);
layoutDiscover = (ViewGroup) itemView.findViewById(R.id.layout_discover);
imgDiscoverAvatar = (WPNetworkImageView) layoutDiscover.findViewById(R.id.image_discover_avatar);
txtDiscover = (TextView) layoutDiscover.findViewById(R.id.text_discover);
thumbnailStrip = (ReaderThumbnailStrip) itemView.findViewById(R.id.thumbnail_strip);
layoutPostHeader = (ViewGroup) itemView.findViewById(R.id.layout_post_header);
followButton = (ReaderFollowButton) layoutPostHeader.findViewById(R.id.follow_button);
// post header isn't shown when there's a site header, so add a bit more
// padding above the title
if (hasSiteHeader()) {
int extraPadding = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.margin_medium);
txtTitle.setPadding(
txtTitle.getPaddingLeft(),
txtTitle.getPaddingTop() + extraPadding,
txtTitle.getPaddingRight(),
txtTitle.getPaddingBottom());
}
ReaderUtils.setBackgroundToRoundRipple(imgMore);
}
}
class SiteHeaderViewHolder extends RecyclerView.ViewHolder {
private final ReaderSiteHeaderView mSiteHeaderView;
public SiteHeaderViewHolder(View itemView) {
super(itemView);
mSiteHeaderView = (ReaderSiteHeaderView) itemView;
}
}
class TagHeaderViewHolder extends RecyclerView.ViewHolder {
private final ReaderTagHeaderView mTagHeaderView;
public TagHeaderViewHolder(View itemView) {
super(itemView);
mTagHeaderView = (ReaderTagHeaderView) itemView;
}
}
class GapMarkerViewHolder extends RecyclerView.ViewHolder {
private final ReaderGapMarkerView mGapMarkerView;
public GapMarkerViewHolder(View itemView) {
super(itemView);
mGapMarkerView = (ReaderGapMarkerView) itemView;
}
}
@Override
public int getItemViewType(int position) {
if (position == 0 && hasSiteHeader()) {
// first item is a ReaderSiteHeaderView
return VIEW_TYPE_SITE_HEADER;
} else if (position == 0 && hasTagHeader()) {
// first item is a ReaderTagHeaderView
return VIEW_TYPE_TAG_HEADER;
} else if (position == mGapMarkerPosition) {
return VIEW_TYPE_GAP_MARKER;
} else if (getItem(position).isXpost()) {
return VIEW_TYPE_XPOST;
} else {
return VIEW_TYPE_POST;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
switch (viewType) {
case VIEW_TYPE_SITE_HEADER:
return new SiteHeaderViewHolder(new ReaderSiteHeaderView(context));
case VIEW_TYPE_TAG_HEADER:
return new TagHeaderViewHolder(new ReaderTagHeaderView(context));
case VIEW_TYPE_GAP_MARKER:
return new GapMarkerViewHolder(new ReaderGapMarkerView(context));
case VIEW_TYPE_XPOST:
View xpostView = LayoutInflater.from(context).inflate(R.layout.reader_cardview_xpost, parent, false);
return new ReaderXPostViewHolder(xpostView);
default:
View postView = LayoutInflater.from(context).inflate(R.layout.reader_cardview_post, parent, false);
return new ReaderPostViewHolder(postView);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ReaderPostViewHolder) {
renderPost(position, (ReaderPostViewHolder) holder);
} else if (holder instanceof ReaderXPostViewHolder) {
renderXPost(position, (ReaderXPostViewHolder) holder);
} else if (holder instanceof SiteHeaderViewHolder) {
SiteHeaderViewHolder siteHolder = (SiteHeaderViewHolder) holder;
siteHolder.mSiteHeaderView.setOnBlogInfoLoadedListener(mBlogInfoLoadedListener);
if (isDiscover()) {
siteHolder.mSiteHeaderView.loadBlogInfo(ReaderConstants.DISCOVER_SITE_ID, 0);
} else {
siteHolder.mSiteHeaderView.loadBlogInfo(mCurrentBlogId, mCurrentFeedId);
}
} else if (holder instanceof TagHeaderViewHolder) {
TagHeaderViewHolder tagHolder = (TagHeaderViewHolder) holder;
tagHolder.mTagHeaderView.setCurrentTag(mCurrentTag);
} else if (holder instanceof GapMarkerViewHolder) {
GapMarkerViewHolder gapHolder = (GapMarkerViewHolder) holder;
gapHolder.mGapMarkerView.setCurrentTag(mCurrentTag);
}
}
private void renderXPost(int position, ReaderXPostViewHolder holder) {
final ReaderPost post = getItem(position);
if (post.hasPostAvatar()) {
holder.imgAvatar.setImageUrl(
post.getPostAvatarForDisplay(mAvatarSzSmall), WPNetworkImageView.ImageType.AVATAR);
} else {
holder.imgAvatar.showDefaultGravatarImage();
}
if (post.hasBlogUrl()) {
holder.imgBlavatar.setImageUrl(
post.getPostBlavatarForDisplay(mAvatarSzMedium), WPNetworkImageView.ImageType.BLAVATAR);
} else {
holder.imgBlavatar.showDefaultBlavatarImage();
}
holder.txtTitle.setText(ReaderXPostUtils.getXPostTitle(post));
holder.txtSubtitle.setText(ReaderXPostUtils.getXPostSubtitleHtml(post));
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPostSelectedListener != null) {
mPostSelectedListener.onPostSelected(post);
}
}
});
checkLoadMore(position);
}
private void renderPost(int position, ReaderPostViewHolder holder) {
final ReaderPost post = getItem(position);
ReaderTypes.ReaderPostListType postListType = getPostListType();
holder.txtTitle.setText(post.getTitle());
String timestamp = DateTimeUtils.javaDateToTimeSpan(post.getDisplayDate(), WordPress.getContext());
if (post.hasAuthorName()) {
holder.txtDateline.setText(post.getAuthorName() + ReaderConstants.UNICODE_BULLET_WITH_SPACE + timestamp);
} else if (post.hasBlogName()) {
holder.txtDateline.setText(post.getBlogName() + ReaderConstants.UNICODE_BULLET_WITH_SPACE + timestamp);
} else {
holder.txtDateline.setText(timestamp);
}
if (post.hasPostAvatar()) {
holder.imgAvatar.setImageUrl(
post.getPostAvatarForDisplay(mAvatarSzTiny), WPNetworkImageView.ImageType.AVATAR);
} else {
holder.imgAvatar.showDefaultGravatarImage();
}
// post header isn't show when there's a site header
if (hasSiteHeader()) {
holder.layoutPostHeader.setVisibility(View.GONE);
} else {
holder.layoutPostHeader.setVisibility(View.VISIBLE);
// show blog preview when post header is tapped
holder.layoutPostHeader.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ReaderActivityLauncher.showReaderBlogPreview(view.getContext(), post);
}
});
}
if (post.hasBlogUrl()) {
String imageUrl = GravatarUtils.blavatarFromUrl(post.getBlogUrl(), mAvatarSzMedium);
holder.imgBlavatar.setImageUrl(imageUrl, WPNetworkImageView.ImageType.BLAVATAR);
holder.txtDomain.setText(UrlUtils.getHost(post.getBlogUrl()));
} else {
holder.imgBlavatar.showDefaultBlavatarImage();
holder.txtDomain.setText(null);
}
if (post.hasBlogName()) {
holder.txtBlogName.setText(post.getBlogName());
} else if (post.hasAuthorName()) {
holder.txtBlogName.setText(post.getAuthorName());
} else {
holder.txtBlogName.setText(null);
}
if (post.hasExcerpt()) {
holder.txtText.setVisibility(View.VISIBLE);
holder.txtText.setText(post.getExcerpt());
} else {
holder.txtText.setVisibility(View.GONE);
}
final int titleMargin;
if (post.hasFeaturedImage()) {
final String imageUrl = post.getFeaturedImageForDisplay(mPhotonWidth, mPhotonHeight);
holder.imgFeatured.setImageUrl(imageUrl, WPNetworkImageView.ImageType.PHOTO);
holder.imgFeatured.setVisibility(View.VISIBLE);
titleMargin = mMarginLarge;
} else if (post.hasFeaturedVideo() && WPNetworkImageView.canShowVideoThumbnail(post.getFeaturedVideo())) {
holder.imgFeatured.setVideoUrl(post.postId, post.getFeaturedVideo());
holder.imgFeatured.setVisibility(View.VISIBLE);
titleMargin = mMarginLarge;
} else {
holder.imgFeatured.setVisibility(View.GONE);
titleMargin = (holder.layoutPostHeader.getVisibility() == View.VISIBLE ? 0 : mMarginLarge);
}
// set the top margin of the title based on whether there's a featured image and post header
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.txtTitle.getLayoutParams();
params.topMargin = titleMargin;
// show the best tag for this post
final String tagToDisplay = (mCurrentTag != null ? post.getTagForDisplay(mCurrentTag.getTagSlug()) : null);
if (!TextUtils.isEmpty(tagToDisplay)) {
holder.txtTag.setText(ReaderUtils.makeHashTag(tagToDisplay));
holder.txtTag.setVisibility(View.VISIBLE);
holder.txtTag.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnTagSelectedListener != null) {
mOnTagSelectedListener.onTagSelected(tagToDisplay);
}
}
});
} else {
holder.txtTag.setVisibility(View.GONE);
holder.txtTag.setOnClickListener(null);
}
showLikes(holder, post);
showComments(holder, post);
// more menu only shows for followed tags
if (!mIsLoggedOutReader && postListType == ReaderTypes.ReaderPostListType.TAG_FOLLOWED) {
holder.imgMore.setVisibility(View.VISIBLE);
holder.imgMore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mOnPostPopupListener != null) {
mOnPostPopupListener.onShowPostPopup(view, post);
}
}
});
} else {
holder.imgMore.setVisibility(View.GONE);
holder.imgMore.setOnClickListener(null);
}
// follow button doesn't show for "Followed Sites" or when there's a site header (Discover, site preview)
boolean showFollowButton = !hasSiteHeader()
&& !mIsLoggedOutReader
&& !isFollowedSites();
if (showFollowButton) {
holder.followButton.setIsFollowed(post.isFollowedByCurrentUser);
holder.followButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
toggleFollow(view.getContext(), view, post);
}
});
holder.followButton.setVisibility(View.VISIBLE);
} else {
holder.followButton.setVisibility(View.GONE);
}
// attribution section for discover posts
if (post.isDiscoverPost()) {
showDiscoverData(holder, post);
} else {
holder.layoutDiscover.setVisibility(View.GONE);
}
// if this post has attachments or contains a gallery, scan it for images and show a
// thumbnail strip of them - note that the thumbnail strip will take care of making
// itself visible
if (post.hasAttachments() || post.isGallery()) {
holder.thumbnailStrip.loadThumbnails(post.blogId, post.postId, post.isPrivate);
} else {
holder.thumbnailStrip.setVisibility(View.GONE);
}
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPostSelectedListener != null) {
mPostSelectedListener.onPostSelected(post);
}
}
});
checkLoadMore(position);
// if we haven't already rendered this post and it has a "railcar" attached to it, add it
// to the rendered list and record the TrainTracks render event
if (post.hasRailcar() && !mRenderedIds.contains(post.getPseudoId())) {
mRenderedIds.add(post.getPseudoId());
AnalyticsUtils.trackRailcarRender(post.getRailcarJson());
}
}
/*
* if we're nearing the end of the posts, fire request to load more
*/
private void checkLoadMore(int position) {
if (mCanRequestMorePosts
&& mDataRequestedListener != null
&& (position >= getItemCount() - 1)) {
mDataRequestedListener.onRequestData();
}
}
private void showDiscoverData(final ReaderPostViewHolder postHolder,
final ReaderPost post) {
final ReaderPostDiscoverData discoverData = post.getDiscoverData();
if (discoverData == null) {
postHolder.layoutDiscover.setVisibility(View.GONE);
return;
}
postHolder.layoutDiscover.setVisibility(View.VISIBLE);
postHolder.txtDiscover.setText(discoverData.getAttributionHtml());
switch (discoverData.getDiscoverType()) {
case EDITOR_PICK:
if (discoverData.hasAvatarUrl()) {
postHolder.imgDiscoverAvatar.setImageUrl(GravatarUtils.fixGravatarUrl(discoverData.getAvatarUrl(), mAvatarSzSmall), WPNetworkImageView.ImageType.AVATAR);
} else {
postHolder.imgDiscoverAvatar.showDefaultGravatarImage();
}
// tapping an editor pick opens the source post, which is handled by the existing
// post selection handler
postHolder.layoutDiscover.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPostSelectedListener != null) {
mPostSelectedListener.onPostSelected(post);
}
}
});
break;
case SITE_PICK:
if (discoverData.hasAvatarUrl()) {
postHolder.imgDiscoverAvatar.setImageUrl(
GravatarUtils.fixGravatarUrl(discoverData.getAvatarUrl(), mAvatarSzSmall), WPNetworkImageView.ImageType.BLAVATAR);
} else {
postHolder.imgDiscoverAvatar.showDefaultBlavatarImage();
}
// site picks show "Visit [BlogName]" link - tapping opens the blog preview if
// we have the blogId, if not show blog in internal webView
postHolder.layoutDiscover.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (discoverData.getBlogId() != 0) {
ReaderActivityLauncher.showReaderBlogPreview(
v.getContext(),
discoverData.getBlogId());
} else if (discoverData.hasBlogUrl()) {
ReaderActivityLauncher.openUrl(v.getContext(), discoverData.getBlogUrl());
}
}
});
break;
default:
// something else, so hide discover section
postHolder.layoutDiscover.setVisibility(View.GONE);
break;
}
}
// ********************************************************************************************
public ReaderPostAdapter(Context context, ReaderTypes.ReaderPostListType postListType) {
super();
mPostListType = postListType;
mAvatarSzMedium = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_medium);
mAvatarSzSmall = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_small);
mAvatarSzTiny = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_tiny);
mMarginLarge = context.getResources().getDimensionPixelSize(R.dimen.margin_large);
mIsLoggedOutReader = ReaderUtils.isLoggedOutReader();
int displayWidth = DisplayUtils.getDisplayPixelWidth(context);
int cardMargin = context.getResources().getDimensionPixelSize(R.dimen.reader_card_margin);
mPhotonWidth = displayWidth - (cardMargin * 2);
mPhotonHeight = context.getResources().getDimensionPixelSize(R.dimen.reader_featured_image_height);
setHasStableIds(true);
}
private boolean hasCustomFirstItem() {
return hasSiteHeader() || hasTagHeader();
}
private boolean hasSiteHeader() {
return isDiscover() || getPostListType() == ReaderTypes.ReaderPostListType.BLOG_PREVIEW;
}
private boolean hasTagHeader() {
return getPostListType() == ReaderTypes.ReaderPostListType.TAG_PREVIEW;
}
private boolean isDiscover() {
return mCurrentTag != null && mCurrentTag.isDiscover();
}
private boolean isFollowedSites() {
return mCurrentTag != null && mCurrentTag.isFollowedSites();
}
public void setOnPostSelectedListener(ReaderInterfaces.OnPostSelectedListener listener) {
mPostSelectedListener = listener;
}
public void setOnDataLoadedListener(ReaderInterfaces.DataLoadedListener listener) {
mDataLoadedListener = listener;
}
public void setOnDataRequestedListener(ReaderActions.DataRequestedListener listener) {
mDataRequestedListener = listener;
}
public void setOnPostPopupListener(ReaderInterfaces.OnPostPopupListener onPostPopupListener) {
mOnPostPopupListener = onPostPopupListener;
}
public void setOnBlogInfoLoadedListener(ReaderSiteHeaderView.OnBlogInfoLoadedListener listener) {
mBlogInfoLoadedListener = listener;
}
/*
* called when user clicks a tag
*/
public void setOnTagSelectedListener(ReaderInterfaces.OnTagSelectedListener listener) {
mOnTagSelectedListener = listener;
}
private ReaderTypes.ReaderPostListType getPostListType() {
return (mPostListType != null ? mPostListType : ReaderTypes.DEFAULT_POST_LIST_TYPE);
}
// used when the viewing tagged posts
public void setCurrentTag(ReaderTag tag) {
if (!ReaderTag.isSameTag(tag, mCurrentTag)) {
mCurrentTag = tag;
mRenderedIds.clear();
reload();
}
}
public boolean isCurrentTag(ReaderTag tag) {
return ReaderTag.isSameTag(tag, mCurrentTag);
}
// used when the list type is ReaderPostListType.BLOG_PREVIEW
public void setCurrentBlogAndFeed(long blogId, long feedId) {
if (blogId != mCurrentBlogId || feedId != mCurrentFeedId) {
mCurrentBlogId = blogId;
mCurrentFeedId = feedId;
mRenderedIds.clear();
reload();
}
}
public void clear() {
if (!mPosts.isEmpty()) {
mPosts.clear();
notifyDataSetChanged();
}
}
public void refresh() {
loadPosts();
}
/*
* same as refresh() above but first clears the existing posts
*/
public void reload() {
clear();
loadPosts();
}
public void removePostsInBlog(long blogId) {
int numRemoved = 0;
ReaderPostList postsInBlog = mPosts.getPostsInBlog(blogId);
for (ReaderPost post : postsInBlog) {
int index = mPosts.indexOfPost(post);
if (index > -1) {
numRemoved++;
mPosts.remove(index);
}
}
if (numRemoved > 0) {
notifyDataSetChanged();
}
}
private void loadPosts() {
if (mIsTaskRunning) {
AppLog.w(AppLog.T.READER, "reader posts task already running");
return;
}
new LoadPostsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private ReaderPost getItem(int position) {
if (position == 0 && hasCustomFirstItem()) {
return null;
}
if (position == mGapMarkerPosition) {
return null;
}
int arrayPos = hasCustomFirstItem() ? position - 1 : position;
if (mGapMarkerPosition > -1 && position > mGapMarkerPosition) {
arrayPos--;
}
return mPosts.get(arrayPos);
}
@Override
public int getItemCount() {
if (hasCustomFirstItem()) {
return mPosts.size() + 1;
}
return mPosts.size();
}
public boolean isEmpty() {
return (mPosts == null || mPosts.size() == 0);
}
@Override
public long getItemId(int position) {
if (getItemViewType(position) == VIEW_TYPE_POST) {
return getItem(position).getStableId();
} else {
return ITEM_ID_CUSTOM_VIEW;
}
}
private void showLikes(final ReaderPostViewHolder holder, final ReaderPost post) {
boolean canShowLikes;
if (post.isDiscoverPost()) {
canShowLikes = false;
} else if (mIsLoggedOutReader) {
canShowLikes = post.numLikes > 0;
} else {
canShowLikes = post.canLikePost();
}
if (canShowLikes) {
holder.likeCount.setCount(post.numLikes);
holder.likeCount.setSelected(post.isLikedByCurrentUser);
holder.likeCount.setVisibility(View.VISIBLE);
// can't like when logged out
if (!mIsLoggedOutReader) {
holder.likeCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleLike(v.getContext(), holder, post);
}
});
}
} else {
holder.likeCount.setVisibility(View.GONE);
holder.likeCount.setOnClickListener(null);
}
}
private void showComments(final ReaderPostViewHolder holder, final ReaderPost post) {
boolean canShowComments;
if (post.isDiscoverPost()) {
canShowComments = false;
} else if (mIsLoggedOutReader) {
canShowComments = post.numReplies > 0;
} else {
canShowComments = post.isWP() && !post.isJetpack && (post.isCommentsOpen || post.numReplies > 0);
}
if (canShowComments) {
holder.commentCount.setCount(post.numReplies);
holder.commentCount.setVisibility(View.VISIBLE);
holder.commentCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ReaderActivityLauncher.showReaderComments(v.getContext(), post.blogId, post.postId);
}
});
} else {
holder.commentCount.setVisibility(View.GONE);
holder.commentCount.setOnClickListener(null);
}
}
/*
* triggered when user taps the like button (textView)
*/
private void toggleLike(Context context, ReaderPostViewHolder holder, ReaderPost post) {
if (post == null || !NetworkUtils.checkConnection(context)) {
return;
}
boolean isCurrentlyLiked = ReaderPostTable.isPostLikedByCurrentUser(post);
boolean isAskingToLike = !isCurrentlyLiked;
ReaderAnim.animateLikeButton(holder.likeCount.getImageView(), isAskingToLike);
if (!ReaderPostActions.performLikeAction(post, isAskingToLike)) {
ToastUtils.showToast(context, R.string.reader_toast_err_generic);
return;
}
if (isAskingToLike) {
AnalyticsUtils.trackWithReaderPostDetails(AnalyticsTracker.Stat.READER_ARTICLE_LIKED, post);
// Consider a like to be enough to push a page view - solves a long-standing question
// from folks who ask 'why do I have more likes than page views?'.
ReaderPostActions.bumpPageViewForPost(post);
} else {
AnalyticsUtils.trackWithReaderPostDetails(AnalyticsTracker.Stat.READER_ARTICLE_LIKED, post);
}
// update post in array and on screen
int position = mPosts.indexOfPost(post);
ReaderPost updatedPost = ReaderPostTable.getPost(post.blogId, post.postId, true);
if (updatedPost != null && position > -1) {
mPosts.set(position, updatedPost);
showLikes(holder, updatedPost);
}
}
/*
* triggered when user taps the follow button on a post
*/
private void toggleFollow(final Context context, final View followButton, final ReaderPost post) {
if (post == null || !NetworkUtils.checkConnection(context)) {
return;
}
boolean isCurrentlyFollowed = ReaderPostTable.isPostFollowed(post);
final boolean isAskingToFollow = !isCurrentlyFollowed;
ReaderActions.ActionListener actionListener = new ReaderActions.ActionListener() {
@Override
public void onActionResult(boolean succeeded) {
followButton.setEnabled(true);
if (!succeeded) {
int resId = (isAskingToFollow ? R.string.reader_toast_err_follow_blog : R.string.reader_toast_err_unfollow_blog);
ToastUtils.showToast(context, resId);
setFollowStatusForBlog(post.blogId, !isAskingToFollow);
}
}
};
if (!ReaderBlogActions.followBlogForPost(post, isAskingToFollow, actionListener)) {
ToastUtils.showToast(context, R.string.reader_toast_err_generic);
return;
}
followButton.setEnabled(false);
setFollowStatusForBlog(post.blogId, isAskingToFollow);
}
public void setFollowStatusForBlog(long blogId, boolean isFollowing) {
ReaderPost post;
for (int i = 0; i < mPosts.size(); i++) {
post = mPosts.get(i);
if (post.blogId == blogId && post.isFollowedByCurrentUser != isFollowing) {
post.isFollowedByCurrentUser = isFollowing;
mPosts.set(i, post);
notifyItemChanged(i);
}
}
}
public void removeGapMarker() {
if (mGapMarkerPosition == -1) return;
int position = mGapMarkerPosition;
mGapMarkerPosition = -1;
if (position < getItemCount()) {
notifyItemRemoved(position);
}
}
/*
* AsyncTask to load posts in the current tag
*/
private boolean mIsTaskRunning = false;
private class LoadPostsTask extends AsyncTask<Void, Void, Boolean> {
ReaderPostList allPosts;
@Override
protected void onPreExecute() {
mIsTaskRunning = true;
}
@Override
protected void onCancelled() {
mIsTaskRunning = false;
}
@Override
protected Boolean doInBackground(Void... params) {
int numExisting;
switch (getPostListType()) {
case TAG_PREVIEW:
case TAG_FOLLOWED:
case SEARCH_RESULTS:
allPosts = ReaderPostTable.getPostsWithTag(mCurrentTag, MAX_ROWS, EXCLUDE_TEXT_COLUMN);
numExisting = ReaderPostTable.getNumPostsWithTag(mCurrentTag);
break;
case BLOG_PREVIEW:
if (mCurrentFeedId != 0) {
allPosts = ReaderPostTable.getPostsInFeed(mCurrentFeedId, MAX_ROWS, EXCLUDE_TEXT_COLUMN);
numExisting = ReaderPostTable.getNumPostsInFeed(mCurrentFeedId);
} else {
allPosts = ReaderPostTable.getPostsInBlog(mCurrentBlogId, MAX_ROWS, EXCLUDE_TEXT_COLUMN);
numExisting = ReaderPostTable.getNumPostsInBlog(mCurrentBlogId);
}
break;
default:
return false;
}
if (mPosts.isSameList(allPosts)) {
return false;
}
// if we're not already displaying the max # posts, enable requesting more when
// the user scrolls to the end of the list
mCanRequestMorePosts = (numExisting < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY);
// determine whether a gap marker exists - only applies to tagged posts
mGapMarkerPosition = getGapMarkerPosition();
return true;
}
private int getGapMarkerPosition() {
if (!getPostListType().isTagType()) {
return -1;
}
ReaderBlogIdPostId gapMarkerIds = ReaderPostTable.getGapMarkerIdsForTag(mCurrentTag);
if (gapMarkerIds == null) {
return -1;
}
// find the position of the gap marker post
int gapPosition = allPosts.indexOfIds(gapMarkerIds);
if (gapPosition > -1) {
// increment it because we want the gap marker to appear *below* this post
gapPosition++;
// increment it again if there's a custom first item
if (hasCustomFirstItem()) {
gapPosition++;
}
// remove the gap marker if it's on the last post (edge case but
// it can happen following a purge)
if (gapPosition >= allPosts.size() - 1) {
gapPosition = -1;
AppLog.w(AppLog.T.READER, "gap marker at/after last post, removed");
ReaderPostTable.removeGapMarkerForTag(mCurrentTag);
} else {
AppLog.d(AppLog.T.READER, "gap marker at position " + gapPosition);
}
}
return gapPosition;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
mPosts.clear();
mPosts.addAll(allPosts);
notifyDataSetChanged();
}
if (mDataLoadedListener != null) {
mDataLoadedListener.onDataLoaded(isEmpty());
}
mIsTaskRunning = false;
}
}
}