aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java497
1 files changed, 497 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java
new file mode 100644
index 000000000..8039d13f8
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java
@@ -0,0 +1,497 @@
+package org.wordpress.android.ui.reader.adapters;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.datasets.ReaderCommentTable;
+import org.wordpress.android.datasets.ReaderPostTable;
+import org.wordpress.android.models.ReaderComment;
+import org.wordpress.android.models.ReaderCommentList;
+import org.wordpress.android.models.ReaderPost;
+import org.wordpress.android.ui.comments.CommentUtils;
+import org.wordpress.android.ui.reader.ReaderActivityLauncher;
+import org.wordpress.android.ui.reader.ReaderAnim;
+import org.wordpress.android.ui.reader.ReaderInterfaces;
+import org.wordpress.android.ui.reader.actions.ReaderActions;
+import org.wordpress.android.ui.reader.actions.ReaderCommentActions;
+import org.wordpress.android.ui.reader.utils.ReaderLinkMovementMethod;
+import org.wordpress.android.ui.reader.utils.ReaderUtils;
+import org.wordpress.android.ui.reader.views.ReaderCommentsPostHeaderView;
+import org.wordpress.android.ui.reader.views.ReaderIconCountView;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+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.widgets.WPNetworkImageView;
+
+public class ReaderCommentAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private ReaderPost mPost;
+ private boolean mMoreCommentsExist;
+
+ private static final int MAX_INDENT_LEVEL = 2;
+ private final int mIndentPerLevel;
+ private final int mAvatarSz;
+ private final int mContentWidth;
+
+ private long mHighlightCommentId = 0;
+ private boolean mShowProgressForHighlightedComment = false;
+ private final boolean mIsPrivatePost;
+ private final boolean mIsLoggedOutReader;
+ private boolean mIsHeaderClickEnabled;
+
+ private final int mColorAuthor;
+ private final int mColorNotAuthor;
+ private final int mColorHighlight;
+
+ private static final int VIEW_TYPE_HEADER = 1;
+ private static final int VIEW_TYPE_COMMENT = 2;
+
+ private static final long ID_HEADER = -1L;
+
+ private static final int NUM_HEADERS = 1;
+
+ public interface RequestReplyListener {
+ void onRequestReply(long commentId);
+ }
+
+ private ReaderCommentList mComments = new ReaderCommentList();
+ private RequestReplyListener mReplyListener;
+ private ReaderInterfaces.DataLoadedListener mDataLoadedListener;
+ private ReaderActions.DataRequestedListener mDataRequestedListener;
+
+ class CommentHolder extends RecyclerView.ViewHolder {
+ private final ViewGroup container;
+ private final TextView txtAuthor;
+ private final TextView txtText;
+ private final TextView txtDate;
+
+ private final WPNetworkImageView imgAvatar;
+ private final View spacerIndent;
+ private final ProgressBar progress;
+
+ private final TextView txtReply;
+ private final ImageView imgReply;
+
+ private final ReaderIconCountView countLikes;
+
+ public CommentHolder(View view) {
+ super(view);
+
+ container = (ViewGroup) view.findViewById(R.id.layout_container);
+
+ txtAuthor = (TextView) view.findViewById(R.id.text_comment_author);
+ txtText = (TextView) view.findViewById(R.id.text_comment_text);
+ txtDate = (TextView) view.findViewById(R.id.text_comment_date);
+
+ txtReply = (TextView) view.findViewById(R.id.text_comment_reply);
+ imgReply = (ImageView) view.findViewById(R.id.image_comment_reply);
+
+ imgAvatar = (WPNetworkImageView) view.findViewById(R.id.image_comment_avatar);
+ spacerIndent = view.findViewById(R.id.spacer_comment_indent);
+ progress = (ProgressBar) view.findViewById(R.id.progress_comment);
+
+ countLikes = (ReaderIconCountView) view.findViewById(R.id.count_likes);
+
+ txtText.setLinksClickable(true);
+ txtText.setMovementMethod(ReaderLinkMovementMethod.getInstance(mIsPrivatePost));
+ }
+ }
+
+ class PostHeaderHolder extends RecyclerView.ViewHolder {
+ private final ReaderCommentsPostHeaderView mHeaderView;
+
+ public PostHeaderHolder(View view) {
+ super(view);
+ mHeaderView = (ReaderCommentsPostHeaderView) view;
+ }
+ }
+
+ public ReaderCommentAdapter(Context context, ReaderPost post) {
+ mPost = post;
+ mIsPrivatePost = (post != null && post.isPrivate);
+ mIsLoggedOutReader = ReaderUtils.isLoggedOutReader();
+
+ mIndentPerLevel = context.getResources().getDimensionPixelSize(R.dimen.reader_comment_indent_per_level);
+ mAvatarSz = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_extra_small);
+
+ // calculate the max width of comment content
+ int displayWidth = DisplayUtils.getDisplayPixelWidth(context);
+ int cardMargin = context.getResources().getDimensionPixelSize(R.dimen.reader_card_margin);
+ int contentPadding = context.getResources().getDimensionPixelSize(R.dimen.reader_card_content_padding);
+ int mediumMargin = context.getResources().getDimensionPixelSize(R.dimen.margin_medium);
+ mContentWidth = displayWidth - (cardMargin * 2) - (contentPadding * 2) - (mediumMargin * 2);
+
+ mColorAuthor = ContextCompat.getColor(context, R.color.blue_medium);
+ mColorNotAuthor = ContextCompat.getColor(context, R.color.grey_dark);
+ mColorHighlight = ContextCompat.getColor(context, R.color.grey_lighten_30);
+
+ setHasStableIds(true);
+ }
+
+ public void setReplyListener(RequestReplyListener replyListener) {
+ mReplyListener = replyListener;
+ }
+
+ public void setDataLoadedListener(ReaderInterfaces.DataLoadedListener dataLoadedListener) {
+ mDataLoadedListener = dataLoadedListener;
+ }
+
+ public void setDataRequestedListener(ReaderActions.DataRequestedListener dataRequestedListener) {
+ mDataRequestedListener = dataRequestedListener;
+ }
+
+ public void enableHeaderClicks() {
+ mIsHeaderClickEnabled = true;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position == 0 ? VIEW_TYPE_HEADER : VIEW_TYPE_COMMENT;
+ }
+
+ public void refreshComments() {
+ if (mIsTaskRunning) {
+ AppLog.w(T.READER, "reader comment adapter > Load comments task already running");
+ }
+ new LoadCommentsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mComments.size() + NUM_HEADERS;
+ }
+
+ public boolean isEmpty() {
+ return mComments.size() == 0;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_HEADER:
+ View headerView = new ReaderCommentsPostHeaderView(parent.getContext());
+ headerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ return new PostHeaderHolder(headerView);
+ default:
+ View commentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.reader_listitem_comment, parent, false);
+ return new CommentHolder(commentView);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (holder instanceof PostHeaderHolder) {
+ PostHeaderHolder headerHolder = (PostHeaderHolder) holder;
+ headerHolder.mHeaderView.setPost(mPost);
+ if (mIsHeaderClickEnabled) {
+ headerHolder.mHeaderView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ReaderActivityLauncher.showReaderPostDetail(view.getContext(), mPost.blogId, mPost.postId);
+ }
+ });
+ }
+ return;
+ }
+
+ final ReaderComment comment = getItem(position);
+ if (comment == null) {
+ return;
+ }
+
+ CommentHolder commentHolder = (CommentHolder) holder;
+ commentHolder.txtAuthor.setText(comment.getAuthorName());
+
+ java.util.Date dtPublished = DateTimeUtils.dateFromIso8601(comment.getPublished());
+ commentHolder.txtDate.setText(DateTimeUtils.javaDateToTimeSpan(dtPublished, WordPress.getContext()));
+
+ if (comment.hasAuthorAvatar()) {
+ String avatarUrl = GravatarUtils.fixGravatarUrl(comment.getAuthorAvatar(), mAvatarSz);
+ commentHolder.imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR);
+ } else {
+ commentHolder.imgAvatar.showDefaultGravatarImage();
+ }
+
+ // tapping avatar or author name opens blog preview
+ if (comment.hasAuthorBlogId()) {
+ View.OnClickListener authorListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ReaderActivityLauncher.showReaderBlogPreview(
+ view.getContext(),
+ comment.authorBlogId
+ );
+ }
+ };
+ commentHolder.imgAvatar.setOnClickListener(authorListener);
+ commentHolder.txtAuthor.setOnClickListener(authorListener);
+ } else {
+ commentHolder.imgAvatar.setOnClickListener(null);
+ commentHolder.txtAuthor.setOnClickListener(null);
+ }
+
+ // author name uses different color for comments from the post's author
+ if (comment.authorId == mPost.authorId) {
+ commentHolder.txtAuthor.setTextColor(mColorAuthor);
+ } else {
+ commentHolder.txtAuthor.setTextColor(mColorNotAuthor);
+ }
+
+ // show indentation spacer for comments with parents and indent it based on comment level
+ int indentWidth;
+ if (comment.parentId != 0 && comment.level > 0) {
+ indentWidth = Math.min(MAX_INDENT_LEVEL, comment.level) * mIndentPerLevel;
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) commentHolder.spacerIndent.getLayoutParams();
+ params.width = indentWidth;
+ commentHolder.spacerIndent.setVisibility(View.VISIBLE);
+ } else {
+ indentWidth = 0;
+ commentHolder.spacerIndent.setVisibility(View.GONE);
+ }
+
+ int maxImageWidth = mContentWidth - indentWidth;
+ CommentUtils.displayHtmlComment(commentHolder.txtText, comment.getText(), maxImageWidth);
+
+ // different background for highlighted comment, with optional progress bar
+ if (mHighlightCommentId != 0 && mHighlightCommentId == comment.commentId) {
+ commentHolder.container.setBackgroundColor(mColorHighlight);
+ commentHolder.progress.setVisibility(mShowProgressForHighlightedComment ? View.VISIBLE : View.GONE);
+ } else {
+ commentHolder.container.setBackgroundColor(Color.WHITE);
+ commentHolder.progress.setVisibility(View.GONE);
+ }
+
+ if (mIsLoggedOutReader) {
+ commentHolder.txtReply.setVisibility(View.GONE);
+ commentHolder.imgReply.setVisibility(View.GONE);
+ } else if (mReplyListener != null) {
+ // tapping reply icon tells activity to show reply box
+ View.OnClickListener replyClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mReplyListener.onRequestReply(comment.commentId);
+ }
+ };
+ commentHolder.txtReply.setOnClickListener(replyClickListener);
+ commentHolder.imgReply.setOnClickListener(replyClickListener);
+ }
+
+ showLikeStatus(commentHolder, position);
+
+ // if we're nearing the end of the comments and we know more exist on the server,
+ // fire request to load more
+ if (mMoreCommentsExist && mDataRequestedListener != null && (position >= getItemCount() - NUM_HEADERS)) {
+ mDataRequestedListener.onRequestData();
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_HEADER:
+ return ID_HEADER;
+ default:
+ ReaderComment comment = getItem(position);
+ return comment != null ? comment.commentId : 0;
+ }
+ }
+
+ private ReaderComment getItem(int position) {
+ return position == 0 ? null : mComments.get(position - NUM_HEADERS);
+ }
+
+ private void showLikeStatus(final CommentHolder holder, int position) {
+ ReaderComment comment = getItem(position);
+ if (comment == null) {
+ return;
+ }
+
+ if (mPost.canLikePost()) {
+ holder.countLikes.setVisibility(View.VISIBLE);
+ holder.countLikes.setSelected(comment.isLikedByCurrentUser);
+ holder.countLikes.setCount(comment.numLikes);
+
+ if (mIsLoggedOutReader) {
+ holder.countLikes.setEnabled(false);
+ } else {
+ holder.countLikes.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int clickedPosition = holder.getAdapterPosition();
+ toggleLike(v.getContext(), holder, clickedPosition);
+ }
+ });
+ }
+ } else {
+ holder.countLikes.setVisibility(View.GONE);
+ holder.countLikes.setOnClickListener(null);
+ }
+ }
+
+ private void toggleLike(Context context, CommentHolder holder, int position) {
+ if (!NetworkUtils.checkConnection(context)) {
+ return;
+ }
+
+ ReaderComment comment = getItem(position);
+ if (comment == null) {
+ ToastUtils.showToast(context, R.string.reader_toast_err_generic);
+ return;
+ }
+
+ boolean isAskingToLike = !comment.isLikedByCurrentUser;
+ ReaderAnim.animateLikeButton(holder.countLikes.getImageView(), isAskingToLike);
+
+ if (!ReaderCommentActions.performLikeAction(comment, isAskingToLike)) {
+ ToastUtils.showToast(context, R.string.reader_toast_err_generic);
+ return;
+ }
+
+ ReaderComment updatedComment = ReaderCommentTable.getComment(comment.blogId, comment.postId, comment.commentId);
+ if (updatedComment != null) {
+ mComments.set(position - NUM_HEADERS, updatedComment);
+ showLikeStatus(holder, position);
+ }
+ }
+
+ /*
+ * called from post detail activity when user submits a comment
+ */
+ public void addComment(ReaderComment comment) {
+ if (comment == null) {
+ return;
+ }
+
+ // if the comment doesn't have a parent we can just add it to the list of existing
+ // comments - but if it does have a parent, we need to reload the list so that it
+ // appears under its parent and is correctly indented
+ if (comment.parentId == 0) {
+ mComments.add(comment);
+ notifyDataSetChanged();
+ } else {
+ refreshComments();
+ }
+ }
+
+ /*
+ * called from post detail when submitted a comment fails - this removes the "fake" comment
+ * that was inserted while the API call was still being processed
+ */
+ public void removeComment(long commentId) {
+ if (commentId == mHighlightCommentId) {
+ setHighlightCommentId(0, false);
+ }
+
+ int index = mComments.indexOfCommentId(commentId);
+ if (index > -1) {
+ mComments.remove(index);
+ notifyDataSetChanged();
+ }
+ }
+
+ /*
+ * replace the comment that has the passed commentId with another comment
+ */
+ public void replaceComment(long commentId, ReaderComment comment) {
+ int position = positionOfCommentId(commentId);
+ if (position > -1 && mComments.replaceComment(commentId, comment)) {
+ notifyItemChanged(position);
+ }
+ }
+
+ /*
+ * sets the passed comment as highlighted with a different background color and an optional
+ * progress bar (used when posting new comments) - note that we don't call notifyDataSetChanged()
+ * here since in most cases it's unnecessary, so we leave it up to the caller to do that
+ */
+ public void setHighlightCommentId(long commentId, boolean showProgress) {
+ mHighlightCommentId = commentId;
+ mShowProgressForHighlightedComment = showProgress;
+ }
+
+ /*
+ * returns the position of the passed comment in the adapter, taking the header into account
+ */
+ public int positionOfCommentId(long commentId) {
+ int index = mComments.indexOfCommentId(commentId);
+ return index == -1 ? -1 : index + NUM_HEADERS;
+ }
+
+ /*
+ * AsyncTask to load comments for this post
+ */
+ private boolean mIsTaskRunning = false;
+
+ private class LoadCommentsTask extends AsyncTask<Void, Void, Boolean> {
+ private ReaderCommentList tmpComments;
+ private boolean tmpMoreCommentsExist;
+
+ @Override
+ protected void onPreExecute() {
+ mIsTaskRunning = true;
+ }
+
+ @Override
+ protected void onCancelled() {
+ mIsTaskRunning = false;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (mPost == null) {
+ return false;
+ }
+
+ // determine whether more comments can be downloaded by comparing the number of
+ // comments the post says it has with the number of comments actually stored
+ // locally for this post
+ int numServerComments = ReaderPostTable.getNumCommentsForPost(mPost);
+ int numLocalComments = ReaderCommentTable.getNumCommentsForPost(mPost);
+ tmpMoreCommentsExist = (numServerComments > numLocalComments);
+
+ tmpComments = ReaderCommentTable.getCommentsForPost(mPost);
+ return !mComments.isSameList(tmpComments);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mMoreCommentsExist = tmpMoreCommentsExist;
+
+ if (result) {
+ // assign the comments with children sorted under their parents and indent levels applied
+ mComments = ReaderCommentList.getLevelList(tmpComments);
+ notifyDataSetChanged();
+ }
+ if (mDataLoadedListener != null) {
+ mDataLoadedListener.onDataLoaded(isEmpty());
+ }
+ mIsTaskRunning = false;
+ }
+ }
+
+ /*
+ * Set a post to adapter and update relevant information in the post header
+ */
+ public void setPost(ReaderPost post) {
+ if (post != null) {
+ mPost = post;
+ notifyItemChanged(0); //notify header to update itself
+ }
+
+ }
+}