package com.leavjenn.hews.ui.adapter; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Paint; import android.graphics.Typeface; import android.net.Uri; import android.support.customtabs.CustomTabsIntent; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.leavjenn.hews.Constants; import com.leavjenn.hews.R; import com.leavjenn.hews.misc.Utils; import com.leavjenn.hews.misc.ChromeCustomTabsHelper; import com.leavjenn.hews.misc.HTMLCodeTagHandler; import com.leavjenn.hews.misc.SharedPrefsManager; import com.leavjenn.hews.model.Comment; import com.leavjenn.hews.model.HNItem; import com.leavjenn.hews.model.Post; import com.leavjenn.hews.ui.comment.CommentsActivity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class CommentAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int VIEW_TYPE_HEADER = 0; private static final int VIEW_TYPE_COMMENT = 1; private static final int VIEW_TYPE_FOOTER = 2; private int mLoadingState; private int mCommentIndentColorOrange, mCommentIndentColorBg, mCommentIndentWidth; private Typeface mFont; private float mTextSize, mLineHeight; private HTMLCodeTagHandler mHTMLCodeTagHandler; private Map<Long, List<Comment>> mCollapsedChildrenCommentsIndex; private Map<Long, List<Comment>> mCollapsedOlderCommentsIndex; private Context mContext; private RecyclerView mRecyclerView; private ArrayList<HNItem> mItemList; private ArrayList<Comment> mCommentList; private SharedPreferences prefs; public CommentAdapter(Context context, RecyclerView recyclerView) { mContext = context; mRecyclerView = recyclerView; mItemList = new ArrayList<>(); mCommentList = new ArrayList<>(); mHTMLCodeTagHandler = new HTMLCodeTagHandler(); mCollapsedChildrenCommentsIndex = new HashMap<>(); mCollapsedOlderCommentsIndex = new HashMap<>(); prefs = ((CommentsActivity) mContext).getSharedPreferences(); updateCommentPrefs(); setCommentIndentStyle(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View v; RecyclerView.ViewHolder viewHolder = null; switch (viewType) { case VIEW_TYPE_COMMENT: v = LayoutInflater.from(viewGroup.getContext()).inflate( R.layout.list_item_comment, viewGroup, false); CommentViewHolder vh = new CommentViewHolder(v, setupViewHolderClickListener()); vh.tvComment.setTypeface(mFont); vh.tvComment.setPaintFlags(vh.tvComment.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG); vh.tvComment.setTextSize(mTextSize); vh.tvComment.setLineSpacing(0, mLineHeight); vh.setCommentIndentStripeStyle(mCommentIndentColorOrange, mCommentIndentColorBg, mCommentIndentWidth); viewHolder = vh; break; case VIEW_TYPE_HEADER: v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.list_item_comment_header, viewGroup, false); viewHolder = new CommentHeaderViewHolder(v); break; case VIEW_TYPE_FOOTER: v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.list_item_footer, viewGroup, false); viewHolder = new FooterViewHolder(v); } return viewHolder; } private CommentViewHolder.ViewHolderClickListener setupViewHolderClickListener() { return new CommentViewHolder.ViewHolderClickListener() { @Override public void onClick(int position) { if (isInBetweenCommentsCollapsed(position)) { expandInBetweenComments(position); } else { collapseInBetweenComments(position); } } @Override public void onClickComment(int position) { if (isChildrenCommentsCollapsed(position)) { expandChildrenComments(position); } else { collapseChildrenComment(position); } } @Override public void onLongClick(int position) { showCommentOptionDialog(position); } }; } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if (viewHolder instanceof CommentViewHolder) { // which one is better? // if (getItemViewType(position) == VIEW_TYPE_COMMENT) { bindCommentViewHolder(viewHolder, position); } else if (viewHolder instanceof CommentHeaderViewHolder) { bindHeaderViewHolder(viewHolder); } else if (viewHolder instanceof FooterViewHolder) { bindFooterViewHolder(viewHolder); } } private void bindCommentViewHolder(RecyclerView.ViewHolder viewHolder, int position) { CommentViewHolder commentViewHolder = (CommentViewHolder) viewHolder; Comment comment = (Comment) mItemList.get(position); commentViewHolder.setCommentIndent(comment.getLevel()); commentViewHolder.tvTime.setText(Utils.formatTime(comment.getTime())); if (!comment.getDeleted()) { commentViewHolder.tvAuthor.setText(comment.getBy()); } if (mCollapsedChildrenCommentsIndex.containsKey(comment.getCommentId())) { commentViewHolder.tvComment.setText(mContext.getString(R.string.comments_collapsed_prompt, mCollapsedChildrenCommentsIndex.get(comment.getCommentId()).size() + 1)); commentViewHolder.tvComment.setMinLines(2); commentViewHolder.tvComment.setGravity(Gravity.CENTER); } else { setTextViewHTML(commentViewHolder.tvComment, comment.getText()); commentViewHolder.tvComment.setMinLines(Integer.MIN_VALUE); commentViewHolder.tvComment.setGravity(Gravity.LEFT); } if (mCollapsedOlderCommentsIndex.containsKey(comment.getCommentId())) { commentViewHolder.tvCollapseOlderComments.setVisibility(View.VISIBLE); commentViewHolder.tvCollapseOlderComments.setText( mContext.getString(R.string.comments_collapsed_prompt, mCollapsedOlderCommentsIndex.get(comment.getCommentId()).size())); } else { commentViewHolder.tvCollapseOlderComments.setVisibility(View.GONE); } } private void bindHeaderViewHolder(RecyclerView.ViewHolder viewHolder) { CommentHeaderViewHolder commentHeaderViewHolder = (CommentHeaderViewHolder) viewHolder; Post post = (Post) mItemList.get(0); commentHeaderViewHolder.tvTitle.setText(post.getTitle()); Typeface postFont = Typeface.createFromAsset(mContext.getAssets(), SharedPrefsManager.getPostFont(prefs) + ".ttf"); commentHeaderViewHolder.tvTitle.setTypeface(postFont); commentHeaderViewHolder.tvTitle.setTextSize(SharedPrefsManager.getCommentFontSize(prefs) * 1.5f); commentHeaderViewHolder.tvTitle.setPaintFlags( commentHeaderViewHolder.tvTitle.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG); commentHeaderViewHolder.tvUrl.setText(post.getUrl()); commentHeaderViewHolder.tvPoints.setText("+" + String.valueOf(post.getScore())); commentHeaderViewHolder.tvComments.setText(String.valueOf(post.getDescendants()) + (post.getDescendants() > 1 ? " comments" : " comment")); commentHeaderViewHolder.tvTime.setText(Utils.formatTime(post.getTime())); commentHeaderViewHolder.tvPoster.setText("by: " + post.getBy()); if (post.getText() != null && !post.getText().isEmpty()) { commentHeaderViewHolder.tvContent.setVisibility(View.VISIBLE); commentHeaderViewHolder.tvContent.setText(Html.fromHtml(post.getText())); commentHeaderViewHolder.tvContent.setTextSize(SharedPrefsManager.getCommentFontSize(prefs)); Typeface commentFont = Typeface.createFromAsset(mContext.getAssets(), SharedPrefsManager.getCommentFont(prefs) + ".ttf"); commentHeaderViewHolder.tvContent.setTypeface(commentFont); commentHeaderViewHolder.tvContent. setLineSpacing(0, SharedPrefsManager.getCommentLineHeight(prefs)); } } private void bindFooterViewHolder(RecyclerView.ViewHolder viewHolder) { FooterViewHolder footerViewHolder = (FooterViewHolder) viewHolder; if (mLoadingState == Constants.LOADING_FINISH) { footerViewHolder.progressBar.setVisibility(View.GONE); footerViewHolder.tvPrompt.setVisibility(View.GONE); } else if (mLoadingState == Constants.LOADING_PROMPT_NO_CONTENT) { footerViewHolder.progressBar.setVisibility(View.GONE); footerViewHolder.tvPrompt.setText( mContext.getResources().getString(R.string.prompt_no_comments)); } else if (mLoadingState == Constants.LOADING_IN_PROGRESS) { footerViewHolder.progressBar.setVisibility(View.VISIBLE); footerViewHolder.tvPrompt.setVisibility(View.GONE); } else if (mLoadingState == Constants.LOADING_ERROR) { footerViewHolder.progressBar.setVisibility(View.GONE); footerViewHolder.tvPrompt.setText( mContext.getResources().getString(R.string.prompt_comments_loading_error)); } } private void setTextViewHTML(TextView textView, String string) { // trim two trailing blank lines CharSequence sequence = Html.fromHtml(string.replace("<p>", "<br /><br />").replace("\n", "<br />"), null, mHTMLCodeTagHandler); // use Chrome custom tab if it is available if (mContext instanceof CommentsActivity && ((CommentsActivity) mContext).getChromeCustomTabsHelper() != null) { SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence); URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class); for (URLSpan span : urls) { makeLinkClickable(strBuilder, span); } textView.setText(strBuilder); } else { textView.setText(sequence); } } private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) { int start = strBuilder.getSpanStart(span); int end = strBuilder.getSpanEnd(span); int flags = strBuilder.getSpanFlags(span); ClickableSpan clickable = new ClickableSpan() { public void onClick(View view) { Uri uri = Uri.parse(span.getURL()); CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder(); Utils.setupIntentBuilder(intentBuilder, mContext, ((CommentsActivity) mContext).getSharedPreferences()); ChromeCustomTabsHelper.openCustomTab((Activity) mContext, intentBuilder.build(), uri, null); } }; strBuilder.setSpan(clickable, start, end, flags); strBuilder.removeSpan(span); } @Override public int getItemCount() { return mItemList.size(); } @Override public int getItemViewType(int position) { if (mItemList.get(position) instanceof Post) { return VIEW_TYPE_HEADER; } else if (mItemList.get(position) instanceof HNItem.Footer) { return VIEW_TYPE_FOOTER; } else { return VIEW_TYPE_COMMENT; } } public void add(HNItem item) { mItemList.add(item); } public void addHeader(Post post) { mItemList.add(0, post); notifyItemInserted(0); } public void addComment(Comment comment) { mItemList.add(mItemList.size() - 1, comment); notifyItemInserted(mItemList.indexOf(comment)); mCommentList.add(comment); } public void addFooter(HNItem.Footer footer) { mItemList.add(footer); notifyItemInserted(mItemList.size() - 1); } public void addAllComments(List<Comment> commentList) { mItemList.addAll(mItemList.size() - 1, commentList); notifyDataSetChanged(); mCommentList.addAll(commentList); } public void updateFooter(int loadingState) { mLoadingState = loadingState; } public ArrayList<Comment> getCommentList() { return mCommentList; } public void clear() { mItemList.clear(); mCommentList.clear(); } public void collapseChildrenComment(int position) { ArrayList<Comment> childrenComments = new ArrayList<>(); Comment parentComment = (Comment) mItemList.get(position); LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager(); // if a comment header is not visible when collapsing, scroll to it's header if (position != linearLayoutManager.findFirstCompletelyVisibleItemPosition()) { linearLayoutManager.scrollToPosition(position); } for (int curPosition = position + 1; (mItemList.get(curPosition) instanceof Comment && ((Comment) mItemList.get(curPosition)).getLevel() > parentComment.getLevel()); curPosition++) { childrenComments.add((Comment) mItemList.get(curPosition)); } if (!childrenComments.isEmpty()) { mCollapsedChildrenCommentsIndex.put(parentComment.getCommentId(), childrenComments); for (Comment comment : childrenComments) { mItemList.remove(comment); } notifyItemChanged(position); notifyItemRangeRemoved(position + 1, childrenComments.size()); } } public void expandChildrenComments(int position) { Comment parentComment = (Comment) mItemList.get(position); List<Comment> collapsedComments = mCollapsedChildrenCommentsIndex.get(parentComment.getCommentId()); int insertPosition = mItemList.indexOf(parentComment) + 1; for (Comment comment : collapsedComments) { mItemList.add(insertPosition, comment); insertPosition++; } notifyItemRangeInserted(mItemList.indexOf(parentComment) + 1, collapsedComments.size()); notifyItemChanged(position); mCollapsedChildrenCommentsIndex.remove(parentComment.getCommentId()); } public boolean isChildrenCommentsCollapsed(int position) { Comment Comment = (Comment) mItemList.get(position); return mCollapsedChildrenCommentsIndex.containsKey(Comment.getCommentId()); } public void collapseInBetweenComments(int position) { ArrayList<Comment> olderComments = new ArrayList<>(); Comment curComment = (Comment) mItemList.get(position); for (int curPosition = position - 1; curComment.getLevel() > 0 && ((Comment) mItemList.get(curPosition)).getLevel() >= curComment.getLevel() && curPosition > 0; curPosition--) { olderComments.add(0, (Comment) mItemList.get(curPosition)); } if (!olderComments.isEmpty()) { mCollapsedOlderCommentsIndex.put(curComment.getCommentId(), olderComments); for (Comment comment : olderComments) { mItemList.remove(comment); } notifyItemChanged(position); notifyItemRangeRemoved(position - olderComments.size(), olderComments.size()); } } public void expandInBetweenComments(int position) { Comment clingedComment = (Comment) mItemList.get(position); List<Comment> olderComments = mCollapsedOlderCommentsIndex.get(clingedComment.getCommentId()); int insertPosition = position; for (Comment comment : olderComments) { mItemList.add(insertPosition, comment); insertPosition++; } notifyItemChanged(position); notifyItemRangeInserted(position, olderComments.size()); mCollapsedOlderCommentsIndex.remove(clingedComment.getCommentId()); } public boolean isInBetweenCommentsCollapsed(int position) { Comment Comment = (Comment) mItemList.get(position); return mCollapsedOlderCommentsIndex.containsKey(Comment.getCommentId()); } private void showCommentOptionDialog(int position) { final Comment comment = (Comment) mItemList.get(position); ((CommentsActivity) mContext).showCommentOptionDialog(comment); // scrollToPosition() not working ((LinearLayoutManager) mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0); } public void updateCommentPrefs() { mFont = Typeface.createFromAsset(mContext.getAssets(), SharedPrefsManager.getCommentFont(prefs) + ".ttf"); mTextSize = SharedPrefsManager.getCommentFontSize(prefs); mLineHeight = SharedPrefsManager.getCommentLineHeight(prefs); } private void setCommentIndentStyle() { mCommentIndentWidth = Utils.convertDpToPixels(CommentViewHolder.UNIT_COMMENT_INDENT_DP, mContext); if (SharedPrefsManager.getTheme(prefs).equals(SharedPrefsManager.THEME_LIGHT)) { mCommentIndentColorOrange = 0xFFffa726; //orange_400 mCommentIndentColorBg = 0xFFEEEEEE; //grey_200 } else if (SharedPrefsManager.getTheme(prefs).equals(SharedPrefsManager.THEME_DARK)) { mCommentIndentColorOrange = 0xFFe65100; //orange_900 mCommentIndentColorBg = 0xFF212121; //grey_900 } else if (SharedPrefsManager.getTheme(prefs).equals(SharedPrefsManager.THEME_AMOLED_BLACK)) { mCommentIndentColorOrange = 0xFFe65100; //orange_900 mCommentIndentColorBg = 0xFF000000; //black } else { mCommentIndentColorOrange = 0xFFffa726; //orange_400 mCommentIndentColorBg = 0xFFF4ECD8; //sepia } } }