package com.kenny.openimgur.ui.adapters; import android.content.Context; import android.support.v4.util.LongSparseArray; import android.support.v7.widget.RecyclerView; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.ForegroundColorSpan; import android.text.util.Linkify; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.TextView; import com.kenny.openimgur.R; import com.kenny.openimgur.classes.CustomLinkMovement; import com.kenny.openimgur.classes.ImgurBaseObject; import com.kenny.openimgur.classes.ImgurComment; import com.kenny.openimgur.classes.ImgurListener; import com.kenny.openimgur.util.LinkUtils; import java.util.HashSet; import java.util.List; import java.util.Set; import butterknife.BindView; /** * Created by kcampagna on 6/10/15. */ public class CommentAdapter extends BaseRecyclerAdapter<ImgurComment> { private static final float EXPANDED = 135.0f; private static final float COLLAPSED = 0.0f; ImgurListener mListener; private int mSelectedIndex = -1; private String mOP; private final Set<ImgurComment> mExpandedComments = new HashSet<>(); private final LongSparseArray<Integer> mIndicatorMultiples = new LongSparseArray<>(); private int mGreenTextColor; private int mRedTextColor; private int mCommentIndent; public CommentAdapter(Context context, List<ImgurComment> comments, ImgurListener listener) { super(context, comments); mListener = listener; mGreenTextColor = getColor(R.color.notoriety_positive); mRedTextColor = getColor(R.color.notoriety_negative); mCommentIndent = getDimension(R.dimen.comment_padding); } /** * Removes all items from list and ImgurListener is removed */ @Override public void onDestroy() { super.onDestroy(); clear(); mListener = null; } public void clearExpansionInfo() { mExpandedComments.clear(); mIndicatorMultiples.clear(); } @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final CommentViewHolder holder = new CommentViewHolder(inflateView(R.layout.comment_item, parent)); holder.comment.setMovementMethod(CustomLinkMovement.getInstance(mListener)); holder.replies.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mListener != null) mListener.onViewRepliesTap(holder.itemView); } }); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // This will trigger the callback for list item click if (mListener != null) mListener.onLinkTap(holder.itemView, null); } }); return holder; } @Override public void onBindViewHolder(BaseViewHolder holder, int position) { holder.itemView.setTag(holder); CommentViewHolder commentHolder = (CommentViewHolder) holder; final ImgurComment comment = getItem(position); commentHolder.comment.setText(comment.getComment()); commentHolder.author.setText(constructSpan(comment)); Linkify.addLinks(commentHolder.comment, Linkify.WEB_URLS); Linkify.addLinks(commentHolder.comment, LinkUtils.USER_CALLOUT_PATTERN, null); commentHolder.replies.setVisibility(comment.getReplyCount() > 0 ? View.VISIBLE : View.GONE); boolean isExpanded = mExpandedComments.contains(comment); commentHolder.replies.setRotation(isExpanded ? EXPANDED : COLLAPSED); commentHolder.indicator.setVisibility(comment.getParentId() > 0 ? View.VISIBLE : View.GONE); Integer multiple = mIndicatorMultiples.get(comment.getParentId()); RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) commentHolder.itemView.getLayoutParams(); if (multiple != null) { lp.setMargins(multiple * mCommentIndent, 0, 0, 0); } else { lp.setMargins(0, 0, 0, 0); } if (ImgurBaseObject.VOTE_UP.equals(comment.getVote())) { commentHolder.author.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_upvote_green_12dp, 0); } else if (ImgurBaseObject.VOTE_DOWN.equals(comment.getVote())) { commentHolder.author.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_downvote_red_12dp, 0); } else { commentHolder.author.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0); } int bgColor = position == mSelectedIndex ? getColor(R.color.comment_bg_selected) : getColor(android.R.color.transparent); commentHolder.itemView.setBackgroundColor(bgColor); } /** * Creates the spannable object for the authors name, points, and time * * @param comment * @return */ private Spannable constructSpan(ImgurComment comment) { CharSequence date = getDateFormattedTime(comment.getDate() * DateUtils.SECOND_IN_MILLIS); String author = comment.getAuthor(); StringBuilder sb = new StringBuilder(author); boolean isOp = isOP(author); int spanLength = author.length(); String points = getResources().getQuantityString(R.plurals.points, (int) comment.getPoints(), comment.getPoints()); int scoreTextLength = points.length(); if (isOp) { sb.append(" OP"); spanLength += 3; } sb.append(": ").append(points).append(" ").append(date); Spannable span = new SpannableString(sb.toString()); int scoreColor = comment.getPoints() >= 0 ? mGreenTextColor : mRedTextColor; if (isOp) { span.setSpan(new ForegroundColorSpan(mGreenTextColor), author.length() + 1, spanLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } int scoreStart = author.length() + 2; if (isOp) scoreStart += +3; span.setSpan(new ForegroundColorSpan(scoreColor), scoreStart, scoreStart + scoreTextLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return span; } private CharSequence getDateFormattedTime(long commentDate) { long now = System.currentTimeMillis(); long difference = System.currentTimeMillis() - commentDate; return (difference >= 0 && difference <= DateUtils.MINUTE_IN_MILLIS) ? getString(R.string.moments_ago) : DateUtils.getRelativeTimeSpanString( commentDate, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_RELATIVE | DateUtils.FORMAT_ABBREV_ALL); } public void setOP(String op) { mOP = op; } public void expandComments(View view, int position) { ImgurComment comment = getItem(position); if (comment.getReplyCount() <= 0) return; // Should always be the case if (view.getTag() instanceof CommentViewHolder) { ((CommentViewHolder) view.getTag()).replies.animate().rotation(EXPANDED); } mExpandedComments.add(comment); Integer multiple = mIndicatorMultiples.get(comment.getParentId()); if (multiple == null) { if (comment.getParentId() > 0) mIndicatorMultiples.put(comment.getParentId(), 1); mIndicatorMultiples.put(Long.valueOf(comment.getId()), 1); } else { mIndicatorMultiples.put(Long.valueOf(comment.getId()), multiple + 1); } addItems(comment.getReplies(), position + 1); } public void collapseComments(View view, int position) { ImgurComment comment = getItem(position); if (comment.getReplyCount() <= 0) return; // Should always be the case if (view.getTag() instanceof CommentViewHolder) { ((CommentViewHolder) view.getTag()).replies.animate().rotation(COLLAPSED); } position++; mExpandedComments.remove(comment); int endPosition = -1; for (int i = position; i < getItemCount(); i++) { ImgurComment c = getItem(i); if (c.getParentId() == comment.getParentId()) { endPosition = i; break; } else if (mExpandedComments.contains(c)) { // Remove any expanded comments mExpandedComments.remove(c); } } // Didn't find a comment parent to find the ending position, need to traverse the list and find comments to close if (endPosition == -1) { // Find the best parent to collapse to for (int i = position - 2; i >= 0; i--) { ImgurComment possibleParent = getItem(i); // Now do the same steps as above to get a matching parent for (int x = position; x < getItemCount(); x++) { ImgurComment c = getItem(x); if (c.getParentId() == possibleParent.getParentId()) { endPosition = x; break; } else if (mExpandedComments.contains(c)) { // Remove any expanded comments mExpandedComments.remove(c); } } if (endPosition != -1) break; } } // Still didn't find anything, just collapse the number of replies it has as a fail safe if (endPosition == -1) endPosition = position + comment.getReplyCount(); removeItems(position, endPosition); } /** * Sets the currently selected item. If the item selected is the one that is already selected, it is deselected * * @param index * @return If the selected item was already selected */ public boolean setSelectedIndex(int index) { int positionToUpdate; boolean wasSelected; if (index >= 0) { wasSelected = mSelectedIndex == index; if (wasSelected) { positionToUpdate = mSelectedIndex; mSelectedIndex = -1; } else { positionToUpdate = index; mSelectedIndex = index; } } else { positionToUpdate = mSelectedIndex; mSelectedIndex = -1; wasSelected = false; } notifyItemChanged(positionToUpdate); return wasSelected; } /** * Returns if the current comment is expanded * * @param position * @return */ public boolean isExpanded(int position) { return mExpandedComments.contains(getItem(position)); } private boolean isOP(String user) { return !TextUtils.isEmpty(mOP) && mOP.equals(user); } public static class CommentViewHolder extends BaseViewHolder { @BindView(R.id.author) TextView author; @BindView(R.id.comment) TextView comment; @BindView(R.id.replies) ImageButton replies; @BindView(R.id.indicator) View indicator; public CommentViewHolder(View view) { super(view); } } }