diff options
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderPostAdapter.java')
-rw-r--r-- | WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderPostAdapter.java | 962 |
1 files changed, 962 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderPostAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderPostAdapter.java new file mode 100644 index 000000000..e650bc22b --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderPostAdapter.java @@ -0,0 +1,962 @@ +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; + } + } +}
\ No newline at end of file |