package com.airlocksoftware.hackernews.adapter;
import android.content.Context;
import android.content.res.Resources;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import com.airlocksoftware.hackernews.R;
import com.airlocksoftware.hackernews.activity.MainActivity;
import com.airlocksoftware.hackernews.activity.MainActivity.CommentsTab;
import com.airlocksoftware.hackernews.activity.ReplyActivity;
import com.airlocksoftware.hackernews.activity.UserActivity;
import com.airlocksoftware.hackernews.model.Comment;
import com.airlocksoftware.hackernews.model.Story;
import com.airlocksoftware.hackernews.view.SharePopup;
import com.airlocksoftware.holo.adapters.GroupAdapter;
import com.airlocksoftware.holo.image.IconView;
import com.airlocksoftware.holo.type.FontText;
import com.airlocksoftware.holo.utils.AnimUtils;
import com.airlocksoftware.holo.utils.Utils;
import com.airlocksoftware.holo.utils.ViewUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Adapter for CommentsFragment. Uses ViewHolder class to cache Views that need to have data re-bound when it changes.
* **/
public class CommentsAdapter extends GroupAdapter<Comment> {
private Context mContext;
private SharePopup mShare;
private ListView mList;
private Story mParentStory;
private int mContainerWidth = 0;
private class ViewHolder {
Comment comment;
Story story;
public FontText username, commentTxt, foldedTxt, storyTitleTxt, upvoteBtnTxt;
public IconView upvoteIndicator, upvoteBtnIcon;
public View depthMargin, depth, foldedContainer, ctrlContainer, upvoteBtn, userBtn, replyBtn, shareBtn,
ctrlDivider, mainContainer;
}
// Constants
private final int HIGHLIGHT_COLOR;
private final int DEFAULT_COLOR;
private final int[] DEPTH_COLORS = { R.color.depth_orange_light, R.color.depth_yellow, R.color.depth_orange_dark,
R.color.depth_red, R.color.depth_purple };
public CommentsAdapter(Context context, ListView list, SharePopup share) {
mContext = context;
mList = list;
mShare = share;
// preload colors from resources
Resources res = mContext.getResources();
for (int i = 0; i < DEPTH_COLORS.length; i++) {
DEPTH_COLORS[i] = res.getColor(DEPTH_COLORS[i]);
}
HIGHLIGHT_COLOR = res.getColor(R.color.bright_accent);
DEFAULT_COLOR = res.getColor(Utils.getThemedResourceId(mContext, R.attr.textColorPrimary));
}
@Override
public View getView(Comment comment, View convertView, ViewGroup parent) {
return getCommentView(null, comment, convertView, parent);
}
public View getCommentView(Story story, Comment comment, View convertView, ViewGroup parent) {
// if mContainerWidth hasn't been set yet, set it to the width of the parent (with a minimum width of 300dp)
if (mContainerWidth <= 0) {
mContainerWidth = Math.max(parent.getWidth(), Utils.dpToPixels(mContext, 300));
}
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
convertView = inflater.inflate(R.layout.vw_comment, parent, false);
ViewUtils.fixBackgroundRepeat(convertView);
holder = setupNewViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
bindView(story, comment, holder);
return convertView;
}
private ViewHolder setupNewViewHolder(View convertView) {
ViewHolder holder = new ViewHolder();
holder.mainContainer = (ViewGroup) convertView.findViewById(R.id.cnt_main);
holder.username = (FontText) convertView.findViewById(R.id.txt_username);
holder.commentTxt = (FontText) convertView.findViewById(R.id.txt_comment);
holder.upvoteIndicator = (IconView) convertView.findViewById(R.id.icv_upvote_indicator);
holder.depth = convertView.findViewById(R.id.depth);
holder.depthMargin = convertView.findViewById(R.id.depth_margin);
holder.foldedContainer = convertView.findViewById(R.id.cnt_folded);
holder.foldedTxt = (FontText) convertView.findViewById(R.id.txt_folded);
holder.ctrlContainer = convertView.findViewById(R.id.cnt_ctrl);
holder.ctrlDivider = convertView.findViewById(R.id.divider_controls);
holder.upvoteBtn = convertView.findViewById(R.id.btn_upvote);
holder.upvoteBtnIcon = (IconView) convertView.findViewById(R.id.icv_upvote);
holder.upvoteBtnTxt = (FontText) convertView.findViewById(R.id.txt_upvote);
holder.userBtn = convertView.findViewById(R.id.btn_user);
holder.replyBtn = convertView.findViewById(R.id.btn_reply);
holder.shareBtn = convertView.findViewById(R.id.btn_share);
holder.storyTitleTxt = (FontText) convertView.findViewById(R.id.txt_story_title);
holder.upvoteBtn.setTag(holder);
holder.userBtn.setTag(holder);
holder.replyBtn.setTag(holder);
holder.shareBtn.setTag(holder);
holder.storyTitleTxt.setTag(holder);
holder.commentTxt.setTag(holder);
holder.mainContainer.setOnClickListener(foldListener);
holder.mainContainer.setOnLongClickListener(ctrlListener);
holder.foldedContainer.setOnClickListener(foldListener);
holder.foldedContainer.setOnLongClickListener(ctrlListener);
holder.commentTxt.setOnClickListener(foldListener);
holder.commentTxt.setOnLongClickListener(ctrlListener);
holder.commentTxt.setMovementMethod(LinkMovementMethod.getInstance());
holder.userBtn.setOnClickListener(userListener);
// Disabled reply button because it's broken
holder.replyBtn.setVisibility(View.GONE);
// holder.replyBtn.setOnClickListener(replyListener);
holder.shareBtn.setOnClickListener(shareListener);
holder.storyTitleTxt.setOnClickListener(threadListener);
holder.mainContainer.setTag(holder);
holder.foldedContainer.setTag(holder);
holder.commentTxt.setTag(holder);
return holder;
}
private void bindView(Story story, Comment comment, ViewHolder holder) {
// set text & visibility of components
holder.username.setText(comment.username + " \u2022 " + comment.ago);
holder.commentTxt.setText(comment.generateSpannedHtml());
holder.ctrlContainer.setVisibility(View.GONE);
holder.ctrlDivider.setVisibility(View.GONE);
holder.comment = comment;
holder.story = story;
// visibility of reply button TODO always disabled temporarily because reply is broken
// if (mParentStory != null) holder.replyBtn.setVisibility(mParentStory.isArchived ? View.GONE : View.VISIBLE);
holder.replyBtn.setVisibility(View.GONE);
// upvote icon color & click listener
if (comment.isUpvoted) {
holder.upvoteBtnTxt.setText("Upvoted!");
holder.upvoteBtnTxt.setTextColor(HIGHLIGHT_COLOR);
holder.upvoteBtnIcon.iconColor(HIGHLIGHT_COLOR);
holder.upvoteBtn.setOnClickListener(null);
holder.upvoteBtn.setClickable(false);
holder.upvoteIndicator.setVisibility(View.VISIBLE);
} else {
holder.upvoteBtnTxt.setText("Upvote");
holder.upvoteBtnTxt.setTextColor(DEFAULT_COLOR);
holder.upvoteBtnIcon.iconColor(DEFAULT_COLOR);
holder.upvoteBtn.setClickable(true);
holder.upvoteBtn.setOnClickListener(voteListener);
holder.upvoteIndicator.setVisibility(View.GONE);
}
// set depth indicator width and padding
if (comment.depth > 0) {
holder.depth.setVisibility(View.VISIBLE);
holder.depthMargin.setVisibility(View.VISIBLE);
// scale the depth indicators & margins based on the width of the container
float depthWidth = mContainerWidth / 50.0f;
LinearLayout.LayoutParams depthParams = (LinearLayout.LayoutParams) holder.depth.getLayoutParams();
depthParams.width = (int) Math.min(depthWidth, Utils.dpToPixels(mContext, 10.0f)); // max width of 10dp
holder.depth.setLayoutParams(depthParams);
LinearLayout.LayoutParams depthMarginParams = (LinearLayout.LayoutParams) holder.depthMargin.getLayoutParams();
depthMarginParams.width = (int) ((comment.depth - 1) * depthWidth);
holder.depthMargin.setLayoutParams(depthMarginParams);
} else {
holder.depth.setVisibility(View.GONE);
holder.depthMargin.setVisibility(View.GONE);
}
// setup depth indicator color
int depthColorPos = (comment.depth) % DEPTH_COLORS.length;
holder.depth.setBackgroundColor(DEPTH_COLORS[depthColorPos]);
// setup folded indicator
holder.foldedContainer.setVisibility((comment.isFolded) ? View.VISIBLE : View.GONE);
holder.mainContainer.setVisibility((comment.isFolded) ? View.GONE : View.VISIBLE);
if (comment.isFolded) {
String foldedMessage = comment.username + " \u2022 " + comment.ago;
if (comment.mChildCount == 1) {
foldedMessage += "\n(1 child)";
} else if (comment.mChildCount > 1) {
foldedMessage += "\n(" + comment.mChildCount + " children)";
} else {
foldedMessage += "\n(no children)";
}
holder.foldedTxt.setText(foldedMessage);
}
// story title (for ThreadAdapter)
if (story != null) {
holder.storyTitleTxt.setVisibility(View.VISIBLE);
holder.storyTitleTxt.setText("on: " + story.title);
} else {
holder.storyTitleTxt.setVisibility(View.GONE);
}
// remove the top margin if this is on a threads page
// because the storyTitleText takes care of spacing with it's padding
RelativeLayout.LayoutParams usernameParams = (LayoutParams) holder.username.getLayoutParams();
usernameParams.topMargin = Utils.dpToPixels(mContext, (story == null) ? 10 : 0);
holder.username.setLayoutParams(usernameParams);
}
public void setParentStory(Story parent) {
mParentStory = parent;
}
private View.OnClickListener userListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
UserActivity.startUserActivity(mContext, holder.comment.username);
}
};
private View.OnClickListener shareListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
mShare.shareComment(holder.comment);
}
};
private View.OnClickListener replyListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
ReplyActivity.startCommentReplyActivity(mContext, holder.comment);
}
};
private View.OnClickListener voteListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
Comment comment = holder.comment;
if (comment.upvote(mContext)) {
hideControls(holder);
notifyDataSetChanged();
} else {
// login (handled automatically by Comment.upvote();)
}
}
};
private View.OnClickListener threadListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
MainActivity.startCommentsActivity(mContext, null, holder.story, CommentsTab.COMMENTS);
}
};
private View.OnLongClickListener ctrlListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
if (holder.ctrlContainer.getVisibility() == View.GONE) {
int scrollY = getScrollY(v);
if (scrollY > 0) {
// longer scroll distances need longer scroll times
int millis = 100 + scrollY * 2;
// TODO smooth scroll seems to be canceled by touch_up
mList.smoothScrollBy(scrollY, millis);
}
showControls(holder);
} else {
hideControls(holder);
}
return true;
}
private int getScrollY(View v) {
int viewHeight = v.getHeight();
int ctrlContainerHeight = mContext.getResources()
.getDimensionPixelSize(R.dimen.slideout_ctrl_height);
int windowLoc = Utils.getWindowLocation(mContext, v).y;
int statusHeight = Utils.getStatusBarHeight(mContext);
int screenHeight = Utils.getScreenSize(mContext).y;
int scrollY = viewHeight + windowLoc + ctrlContainerHeight + statusHeight - screenHeight;
return scrollY;
}
};
private void hideControls(ViewHolder holder) {
Animation outAnim = AnimationUtils.loadAnimation(mContext, R.anim.ctrl_scale_up);
AnimUtils.startOutAnimation(holder.ctrlContainer, outAnim);
holder.ctrlDivider.setVisibility(View.GONE);
}
private void showControls(ViewHolder holder) {
Animation inAnim = AnimationUtils.loadAnimation(mContext, R.anim.ctrl_scale_down);
AnimUtils.startInAnimation(holder.ctrlContainer, inAnim);
holder.ctrlDivider.setVisibility(View.VISIBLE);
}
/**
* When the user clicks on the body of the comment, check if it's a link. If it isn't, the fold the comment & all it's
* children.
**/
private View.OnClickListener foldListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
// check if this click was on a link
boolean clickTargetIsLink = holder.commentTxt.getSelectionStart() != -1
&& holder.commentTxt.getSelectionEnd() != -1;
if (!clickTargetIsLink) {
Comment parent = holder.comment;
// hide child comments (if there are any)
if (!parent.isFolded) {
// get CommentsAdapter list of children
List<Comment> allChildren = CommentsAdapter.this.getArray();
// either create or clear the parent.mChildren List<Comment>
if (parent.mChildren == null) {
parent.mChildren = new ArrayList<Comment>();
} else parent.mChildren.clear();
// find index of parent comment, initialize foldedCommentsCount
int startIndex = allChildren.indexOf(parent) + 1;
int foldedCommentsCount = 0;
// for comments with depth < currentComment.depth, move it to parent.mChildren
for (int i = startIndex; i < allChildren.size(); i++) {
Comment comment = allChildren.get(i);
if (comment.depth > parent.depth) {
parent.mChildren.add(comment);
// be sure to include current comments' folded children in foldedCommentsCount
foldedCommentsCount += 1 + comment.mChildCount;
} else {
// we've got a comment with depth > parent.depth, so we're done folding
if (parent.mChildren.size() > 0) {
allChildren.removeAll(parent.mChildren);
parent.mChildCount = foldedCommentsCount;
}
break;
}
}
// scroll to top of folded comment
int scrollY = getScrollY(holder);
if (scrollY < 0) {
// long scroll distances need bigger scroll times
int millis = 100 + Math.abs(scrollY) * 2;
mList.smoothScrollBy(scrollY, millis);
}
} else if (parent.isFolded && parent.mChildCount > 0) {
// unfold comments (if there are any)
parent.mChildCount = 0;
List<Comment> comments = CommentsAdapter.this.getArray();
int startIndex = comments.indexOf(parent) + 1;
comments.addAll(startIndex, parent.mChildren);
}
// update isFolded
parent.isFolded = !parent.isFolded;
notifyDataSetChanged();
} else {
// they clicked on a link - do nothing it should already be taken care of by autolink
}
}
private int getScrollY(ViewHolder holder) {
int windowLoc = Utils.getWindowLocation(mContext, holder.mainContainer).y;
int statusHeight = Utils.getStatusBarHeight(mContext);
int foldHeight = mContext.getResources()
.getDimensionPixelSize(R.dimen.folded_comment_height);
int scrollY = windowLoc - statusHeight - foldHeight;
return scrollY;
}
};
}