package com.chrome.codereview.tablet; import android.content.Context; import android.text.TextUtils; import android.util.Pair; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import com.chrome.codereview.DiffAdapter; import com.chrome.codereview.R; import com.chrome.codereview.model.Comment; import com.chrome.codereview.model.FileDiff; import com.chrome.codereview.utils.ViewUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /** * Created by sergeyv on 21/6/14. */ public class SideBySideDiffAdapter extends DiffAdapter { private static final int TYPE_SKIP = 0; private static final int TYPE_DIFF = 1; private static final int TYPE_COMMENTS = 2; private static final int NO_LINE_NUMBER = -1; @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { } private static class CommentPair { Comment left; Comment right; } private static class SkippingLine { int skippedLines = 0; } private static class Content { private int leftLineNumber = NO_LINE_NUMBER; private int rightLineNumber = NO_LINE_NUMBER; private boolean isChanged = true; private String left = ""; private String right = ""; } private final List<Object> mergedDiffLines; public SideBySideDiffAdapter(Context context, FileDiff diff, List<Comment> comments) { super(context, diff, comments); mergedDiffLines = new ArrayList<Object>(diff.content().size()); LinkedList<Content> left = new LinkedList<Content>(); for (FileDiff.DiffLine diffLine : diff.content()) { switch (diffLine.type()) { case MARKER: mergedDiffLines.addAll(left); left.clear(); mergedDiffLines.add(new SkippingLine()); break; case BOTH_SIDE: mergedDiffLines.addAll(left); left.clear(); Content content = new Content(); content.isChanged = false; content.leftLineNumber = diffLine.leftLineNumber(); content.rightLineNumber = diffLine.rightLineNumber(); content.left = diffLine.text().substring(1); content.right = diffLine.text().substring(1); mergedDiffLines.add(content); break; case LEFT: Content leftContent = new Content(); leftContent.leftLineNumber = diffLine.leftLineNumber(); leftContent.left = diffLine.text().substring(1); left.add(leftContent); break; case RIGHT: Content rightContent = left.isEmpty() ? new Content() : left.removeFirst(); rightContent.right = diffLine.text().substring(1); rightContent.rightLineNumber = diffLine.rightLineNumber(); mergedDiffLines.add(rightContent); break; } } mergedDiffLines.addAll(left); Content previousContent = null; SkippingLine skippingLine = null; for (Object object : mergedDiffLines) { if (object instanceof SkippingLine) { skippingLine = previousContent != null ? (SkippingLine) object : null; continue; } Content content = (Content) object; if (skippingLine != null) { if (content.rightLineNumber != NO_LINE_NUMBER && previousContent.rightLineNumber != NO_LINE_NUMBER) { skippingLine.skippedLines = content.rightLineNumber - previousContent.rightLineNumber - 1; } if (content.leftLineNumber != NO_LINE_NUMBER && previousContent.leftLineNumber != NO_LINE_NUMBER) { skippingLine.skippedLines = content.leftLineNumber - previousContent.leftLineNumber - 1; } skippingLine = null; } previousContent = content; } for (Iterator<Object> iterator = mergedDiffLines.iterator(); iterator.hasNext(); ) { Object object = iterator.next(); if (object instanceof SkippingLine) { SkippingLine skip = (SkippingLine) object; if (skip.skippedLines == 0) { iterator.remove(); } } } resetComments(comments); } @Override public int getItemViewType(int position) { Object object = linesWithComments.get(position); if (object instanceof SkippingLine) { return TYPE_SKIP; } else { return object instanceof CommentPair ? TYPE_COMMENTS : TYPE_DIFF; } } @Override public int getViewTypeCount() { return 3; } @Override public View getView(int position, View convertView, ViewGroup parent) { switch (getItemViewType(position)) { case TYPE_DIFF: return getDiffView((Content) linesWithComments.get(position), convertView, parent); case TYPE_COMMENTS: return getCommentsView((CommentPair) linesWithComments.get(position), convertView, parent); case TYPE_SKIP: return getSkipLinesView((SkippingLine) linesWithComments.get(position), convertView, parent); } throw new IllegalStateException("Unreachable"); } private View getCommentsView(CommentPair commentPair, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.double_comment_item, parent, false); } View leftCommentView = convertView.findViewById(R.id.left_comment); View rightCommentView = convertView.findViewById(R.id.right_comment); leftCommentView.setVisibility(commentPair.left != null ? View.VISIBLE : View.INVISIBLE); rightCommentView.setVisibility(commentPair.right != null ? View.VISIBLE : View.INVISIBLE); if (commentPair.left != null) { fillCommentView(commentPair.left, leftCommentView); } if (commentPair.right != null) { fillCommentView(commentPair.right, rightCommentView); } return convertView; } private View getSkipLinesView(SkippingLine skippingLine, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.skipping_line, parent, false); } ViewUtils.setText(convertView, android.R.id.text1, context.getString(R.string.skipping_lines, skippingLine.skippedLines)); return convertView; } private void initDiffLines(View partView, String line, int background, int lineNumber) { int backgroundRes = lineNumber != NO_LINE_NUMBER ? background : R.drawable.diff_no_line_bg; partView.setBackgroundDrawable(context.getResources().getDrawable(backgroundRes)); ViewUtils.setText(partView, R.id.line, line); ViewUtils.setText(partView, R.id.lineNumber, lineNumber != NO_LINE_NUMBER ? lineNumber + "" : ""); partView.setTag(lineNumber); if (!TextUtils.isEmpty(line)) { partView.setOnClickListener(this); } } private View getDiffView(Content content, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.side_by_side_diff_item, parent, false); } int leftBg = content.isChanged ? R.drawable.diff_removed_line_bg : R.drawable.diff_default_line_bg; int rightBg = content.isChanged ? R.drawable.diff_added_line_bg : R.drawable.diff_default_line_bg; initDiffLines(convertView.findViewById(R.id.left), content.left, leftBg, content.leftLineNumber); initDiffLines(convertView.findViewById(R.id.right), content.right, rightBg, content.rightLineNumber); return convertView; } @Override protected void rebuildWithComments(HashMap<Pair<Integer, Boolean>, List<Comment>> lineToComments) { for (Object object : mergedDiffLines) { linesWithComments.add(object); if (object instanceof SkippingLine) { continue; } Content content = (Content) object; Iterator<Comment> leftIterator = iteratorFor(lineToComments, content.leftLineNumber, true); Iterator<Comment> rightIterator = iteratorFor(lineToComments, content.rightLineNumber, false); while (leftIterator.hasNext() || rightIterator.hasNext()) { Comment leftComment = leftIterator.hasNext() ? leftIterator.next() : null; Comment rightComment = rightIterator.hasNext() ? rightIterator.next() : null; CommentPair commentPair = new CommentPair(); commentPair.left = leftComment; commentPair.right = rightComment; linesWithComments.add(commentPair); } } } @Override public void onClick(View v) { if (v.getId() == R.id.left || v.getId() == R.id.right) { int lineNumber = (Integer) v.getTag(); commentActionListener.writeComment(lineNumber, v.getId() == R.id.left); return; } super.onClick(v); } private static Iterator<Comment> iteratorFor(HashMap<Pair<Integer, Boolean>, List<Comment>> lineToComments, int lineNumber, boolean left) { List<Comment> leftComments = lineToComments.get(new Pair<Integer, Boolean>(lineNumber, left)); return leftComments != null ? leftComments.iterator() : Collections.<Comment>emptyList().iterator(); } }