Java tutorial
/* * Copyright (C) 2016 Jorge Ruesga * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ruesga.rview.widget; import android.content.Context; import android.content.res.Resources; import android.databinding.DataBindingUtil; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Keep; import android.support.v4.util.Pair; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextPaint; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import com.ruesga.rview.R; import com.ruesga.rview.databinding.DiffAdviseItemBinding; import com.ruesga.rview.databinding.DiffCommentItemBinding; import com.ruesga.rview.databinding.DiffDecoratorItemBinding; import com.ruesga.rview.databinding.DiffSkipItemBinding; import com.ruesga.rview.databinding.DiffSourceItemBinding; import com.ruesga.rview.databinding.DiffViewBinding; import com.ruesga.rview.gerrit.model.BlameInfo; import com.ruesga.rview.gerrit.model.CommentInfo; import com.ruesga.rview.gerrit.model.DiffInfo; import com.ruesga.rview.misc.SerializationManager; import com.ruesga.rview.misc.TypefaceCache; import com.ruesga.rview.preferences.Constants; import com.ruesga.rview.tasks.AsyncImageDiffProcessor; import com.ruesga.rview.tasks.AsyncImageDiffProcessor.OnImageDiffProcessEndedListener; import com.ruesga.rview.tasks.AsyncTextDiffProcessor; import com.ruesga.rview.tasks.AsyncTextDiffProcessor.OnTextDiffProcessEndedListener; import java.io.File; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; public class DiffView extends FrameLayout { public static final int SIDE_BY_SIDE_MODE = 0; public static final int UNIFIED_MODE = 1; public static final int IMAGE_MODE = 2; private static final int SOURCE_VIEW_TYPE = 0; private static final int SKIP_VIEW_TYPE = 1; private static final int COMMENT_VIEW_TYPE = 2; private static final int ADVISE_VIEW_TYPE = 3; private static final int DECORATOR_VIEW_TYPE = 4; @Keep @SuppressWarnings({ "UnusedParameters", "unused" }) public static class EventHandlers { private final DiffView mView; public EventHandlers(DiffView view) { mView = view; } public void onNewDraftPressed(View v) { if (mView.mCanEdit && mView.mOnCommentListener != null && v.getTag() != null) { String[] s = ((String) v.getTag()).split("/"); mView.mOnCommentListener.onNewDraft(v, Boolean.parseBoolean(s[0]), Integer.valueOf(s[1])); } } public void onReplyPressed(View v) { if (mView.mOnCommentListener != null) { String[] s = ((String) v.getTag()).split("/"); Integer line = s[2] == null || s[2].equals("null") ? null : Integer.valueOf(s[2]); mView.mOnCommentListener.onReply(v, s[0], s[1], line); } } public void onDonePressed(View v) { if (mView.mOnCommentListener != null) { String[] s = ((String) v.getTag()).split("/"); Integer line = s[2] == null || s[2].equals("null") ? null : Integer.valueOf(s[2]); mView.mOnCommentListener.onDone(v, s[0], s[1], line); } } public void onEditPressed(View v) { if (mView.mOnCommentListener != null) { String[] s = ((String) v.getTag()).split("/"); String msg = (String) v.getTag(R.id.tag_key); Integer line = s[3] == null || s[3].equals("null") ? null : Integer.valueOf(s[3]); mView.mOnCommentListener.onEditDraft(v, s[0], s[1], s[2], line, msg); } } public void onDeletePressed(View v) { if (mView.mOnCommentListener != null) { String[] s = ((String) v.getTag()).split("/"); mView.mOnCommentListener.onDeleteDraft(v, s[0], s[1]); } } public void onSkipLinePressed(View v) { int position = (int) v.getTag(); mView.onSkipLinePressed(position); } public void onSkipLineUpPressed(View v) { int position = (int) v.getTag(); mView.onSkipUpLinePressed(position); } public void onSkipLineDownPressed(View v) { int position = (int) v.getTag(); mView.onSkipDownLinePressed(position); } } public interface OnCommentListener { void onNewDraft(View v, boolean left, Integer line); void onReply(View v, String revisionId, String commentId, Integer line); void onDone(View v, String revisionId, String commentId, Integer line); void onEditDraft(View v, String revisionId, String draftId, String inReplyTo, Integer line, String msg); void onDeleteDraft(View v, String revisionId, String draftId); } private static class DiffSourceViewHolder extends RecyclerView.ViewHolder { private DiffSourceItemBinding mBinding; DiffSourceViewHolder(DiffSourceItemBinding binding) { super(binding.getRoot()); mBinding = binding; mBinding.executePendingBindings(); } } private static class DiffSkipViewHolder extends RecyclerView.ViewHolder { private DiffSkipItemBinding mBinding; DiffSkipViewHolder(DiffSkipItemBinding binding) { super(binding.getRoot()); mBinding = binding; mBinding.executePendingBindings(); } } private static class DiffCommentViewHolder extends RecyclerView.ViewHolder { private DiffCommentItemBinding mBinding; DiffCommentViewHolder(DiffCommentItemBinding binding) { super(binding.getRoot()); mBinding = binding; mBinding.executePendingBindings(); } } private static class DiffAdviseViewHolder extends RecyclerView.ViewHolder { private DiffAdviseItemBinding mBinding; DiffAdviseViewHolder(DiffAdviseItemBinding binding) { super(binding.getRoot()); mBinding = binding; mBinding.executePendingBindings(); } } private static class DiffDecoratorViewHolder extends RecyclerView.ViewHolder { private DiffDecoratorItemBinding mBinding; DiffDecoratorViewHolder(DiffDecoratorItemBinding binding) { super(binding.getRoot()); mBinding = binding; mBinding.executePendingBindings(); } } public static abstract class AbstractModel { } @Keep public static class DiffInfoModel extends AbstractModel { public int a = -1; public int b = -1; public String lineNumberA; public String lineNumberB; public int colorA; public int colorB; public CharSequence lineA; public CharSequence lineB; public String blameA; public String blameB; } @Keep public static class CommentModel extends AbstractModel { public CommentInfo commentA; public CommentInfo commentB; public boolean isDraft; public DiffInfoModel diff; } @Keep public static class SkipLineModel extends AbstractModel { public String msg; public DiffInfoModel[] skippedLines; } @Keep public static class AdviseModel extends AbstractModel { public String msg; } @Keep public static class DecoratorModel extends AbstractModel { } @Keep public static class DiffViewMeasurement { public float width = -1; public float lineWidth = -1; public float lineNumWidth = -1; private void clear() { width = -1; lineWidth = -1; lineNumWidth = -1; } } @Keep public static class ImageDiffModel { public Drawable left; public String sizeLeft; public String dimensionsLeft; public Drawable right; public String sizeRight; public String dimensionsRight; } @Keep public static class SkipLinesOpHistory { private static final int SKIP_ALL = 0; private static final int SKIP_UP = -1; private static final int SKIP_DOWN = 1; @SerializedName("type") public int type; @SerializedName("at") public final int at; SkipLinesOpHistory(int type, int at) { this.type = type; this.at = at; } } private class DiffAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private LayoutInflater mLayoutInflater; private final List<AbstractModel> mModel = new ArrayList<>(); private final DiffViewMeasurement mDiffViewMeasurement = new DiffViewMeasurement(); private final int mMode; private final List<SkipLinesOpHistory> mSkipLinesOpHistory = new ArrayList<>(); DiffAdapter(int mode) { mLayoutInflater = LayoutInflater.from(getContext()); mMode = mode; } private void update(List<AbstractModel> diffs, List<SkipLinesOpHistory> skipLinesOpHistory) { mSkipLinesOpHistory.clear(); mModel.clear(); mModel.addAll(diffs); mDiffViewMeasurement.clear(); if (skipLinesOpHistory != null) { processSkipLinesOpHistory(skipLinesOpHistory); } refresh(); } private void refresh() { computeViewChildMeasuresIfNeeded(); notifyDataSetChanged(); } private void processSkipLinesOpHistory(List<SkipLinesOpHistory> skipLinesOpHistory) { for (SkipLinesOpHistory op : skipLinesOpHistory) { switch (op.type) { case SkipLinesOpHistory.SKIP_ALL: // skip all showSkippedLinesAt(op.at); break; case SkipLinesOpHistory.SKIP_UP: // skip up showSkippedUpLinesAt(op.at); break; case SkipLinesOpHistory.SKIP_DOWN: // skip down showSkippedDownLinesAt(op.at); break; } } } private void showSkippedLinesAt(int position) { final int at = position; AbstractModel model = mDiffAdapter.mModel.get(position); if (model instanceof SkipLineModel) { SkipLineModel m = (SkipLineModel) model; mDiffAdapter.mModel.remove(position); int count = m.skippedLines.length; for (int i = 0; i < count; i++, position++) { mDiffAdapter.mModel.add(position, m.skippedLines[i]); } } mSkipLinesOpHistory.add(new SkipLinesOpHistory(SkipLinesOpHistory.SKIP_ALL, at)); } private void showSkippedUpLinesAt(int position) { final int at = position; AbstractModel model = mDiffAdapter.mModel.get(position); if (model instanceof SkipLineModel) { SkipLineModel m = (SkipLineModel) model; for (int i = 0; i < AsyncTextDiffProcessor.SKIPPED_LINES; i++, position++) { mDiffAdapter.mModel.add(position, m.skippedLines[i]); } // Trim skipped lines array int from = AsyncTextDiffProcessor.SKIPPED_LINES; int length = m.skippedLines.length - from; DiffInfoModel[] copy = new DiffInfoModel[length]; System.arraycopy(m.skippedLines, from, copy, 0, copy.length); m.skippedLines = copy; m.msg = getResources().getQuantityString(R.plurals.skipped_lines, m.skippedLines.length, m.skippedLines.length); } mSkipLinesOpHistory.add(new SkipLinesOpHistory(SkipLinesOpHistory.SKIP_UP, at)); } private void showSkippedDownLinesAt(int position) { final int at = position; AbstractModel model = mDiffAdapter.mModel.get(position); if (model instanceof SkipLineModel) { SkipLineModel m = (SkipLineModel) model; int count = m.skippedLines.length; int from = m.skippedLines.length - AsyncTextDiffProcessor.SKIPPED_LINES; position++; for (int i = from; i < count; i++, position++) { mDiffAdapter.mModel.add(position, m.skippedLines[i]); } // Trim skipped lines array DiffInfoModel[] copy = new DiffInfoModel[from]; System.arraycopy(m.skippedLines, 0, copy, 0, copy.length); m.skippedLines = copy; m.msg = getResources().getQuantityString(R.plurals.skipped_lines, m.skippedLines.length, m.skippedLines.length); } mSkipLinesOpHistory.add(new SkipLinesOpHistory(SkipLinesOpHistory.SKIP_DOWN, at)); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == SOURCE_VIEW_TYPE) { return new DiffSourceViewHolder( DataBindingUtil.inflate(mLayoutInflater, R.layout.diff_source_item, parent, false)); } else if (viewType == SKIP_VIEW_TYPE) { return new DiffSkipViewHolder( DataBindingUtil.inflate(mLayoutInflater, R.layout.diff_skip_item, parent, false)); } else if (viewType == COMMENT_VIEW_TYPE) { return new DiffCommentViewHolder( DataBindingUtil.inflate(mLayoutInflater, R.layout.diff_comment_item, parent, false)); } else if (viewType == ADVISE_VIEW_TYPE) { return new DiffAdviseViewHolder( DataBindingUtil.inflate(mLayoutInflater, R.layout.diff_advise_item, parent, false)); } else if (viewType == DECORATOR_VIEW_TYPE) { return new DiffDecoratorViewHolder( DataBindingUtil.inflate(mLayoutInflater, R.layout.diff_decorator_item, parent, false)); } return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) { AbstractModel model = mModel.get(position); if (vh instanceof DiffSourceViewHolder) { DiffSourceViewHolder holder = ((DiffSourceViewHolder) vh); DiffInfoModel diff = (DiffInfoModel) model; if (mMode == UNIFIED_MODE) { CharSequence text = diff.lineA != null ? diff.lineA : diff.lineB; holder.mBinding.diffA.setText(text, TextView.BufferType.NORMAL); } else { holder.mBinding.diffA.setText(diff.lineA, TextView.BufferType.NORMAL); holder.mBinding.diffB.setText(diff.lineB, TextView.BufferType.NORMAL); } holder.mBinding.setWrap(isWrapMode()); holder.mBinding.setTextSizeFactor(mTextSizeFactor); holder.mBinding.setMode(mMode); holder.mBinding.setModel(diff); holder.mBinding.setShowBlameA(mShowBlameA); holder.mBinding.setShowBlameB(mShowBlameB); holder.mBinding.setMeasurement(mDiffViewMeasurement); if (mCanEdit) { holder.mBinding.setHandlers(mEventHandlers); } holder.mBinding.executePendingBindings(); } else if (vh instanceof DiffSkipViewHolder) { DiffSkipViewHolder holder = ((DiffSkipViewHolder) vh); SkipLineModel skip = (SkipLineModel) model; holder.mBinding.setWrap(isWrapMode()); holder.mBinding.setModel(skip); holder.mBinding.setHandlers(mEventHandlers); holder.mBinding.setTextSizeFactor(mTextSizeFactor); holder.mBinding.setIndex(position); holder.mBinding.setHasSkipUp( position != 0 && (skip.skippedLines.length > AsyncTextDiffProcessor.SKIPPED_LINES * 2)); // Last item is the item decorator view holder.mBinding.setHasSkipDown(position != (getItemCount() - 2) && (skip.skippedLines.length > AsyncTextDiffProcessor.SKIPPED_LINES * 2)); holder.mBinding.setMeasurement(mDiffViewMeasurement); holder.mBinding.executePendingBindings(); } else if (vh instanceof DiffCommentViewHolder) { DiffCommentViewHolder holder = ((DiffCommentViewHolder) vh); CommentModel comment = (CommentModel) model; holder.mBinding.setCanEdit(mCanEdit); holder.mBinding.setWrap(isWrapMode()); holder.mBinding.setTextSizeFactor(mTextSizeFactor); holder.mBinding.setMode(mMode); holder.mBinding.setModel(comment); holder.mBinding.setMeasurement(mDiffViewMeasurement); holder.mBinding.setHandlers(mEventHandlers); if (comment.commentA != null) { holder.mBinding.actionsA.edit.setTag(R.id.tag_key, comment.commentA.message); } if (comment.commentB != null) { holder.mBinding.actionsB.edit.setTag(R.id.tag_key, comment.commentB.message); } holder.mBinding.executePendingBindings(); } else if (vh instanceof DiffAdviseViewHolder) { DiffAdviseViewHolder holder = ((DiffAdviseViewHolder) vh); AdviseModel advise = (AdviseModel) model; holder.mBinding.setWrap(isWrapMode()); holder.mBinding.setMeasurement(mDiffViewMeasurement); holder.mBinding.setTextSizeFactor(mTextSizeFactor); holder.mBinding.setAdvise(advise.msg); holder.mBinding.executePendingBindings(); } else if (vh instanceof DiffDecoratorViewHolder) { DiffDecoratorViewHolder holder = ((DiffDecoratorViewHolder) vh); holder.mBinding.setWrap(isWrapMode()); holder.mBinding.setMeasurement(mDiffViewMeasurement); holder.mBinding.executePendingBindings(); } if (mLayoutManager instanceof UnwrappedLinearLayoutManager) { ((UnwrappedLinearLayoutManager) mLayoutManager).requestBindViews(); } } @Override public int getItemViewType(int position) { AbstractModel model = mModel.get(position); if (model instanceof DiffInfoModel) { return SOURCE_VIEW_TYPE; } if (model instanceof SkipLineModel) { return SKIP_VIEW_TYPE; } if (model instanceof CommentModel) { return COMMENT_VIEW_TYPE; } if (model instanceof AdviseModel) { return ADVISE_VIEW_TYPE; } return DECORATOR_VIEW_TYPE; } @Override public int getItemCount() { return mModel.size(); } @SuppressWarnings("Convert2streamapi") private void computeViewChildMeasuresIfNeeded() { boolean wrap = isWrapMode(); if (!mModel.isEmpty()) { final Resources res = getResources(); TextPaint paint = new TextPaint(); paint.setTextSize(res.getDimension(R.dimen.diff_line_text_size) * mTextSizeFactor); paint.setTypeface(TypefaceCache.getTypeface(getContext(), TypefaceCache.TF_MONOSPACE)); float padding = res.getDimension(R.dimen.diff_line_text_padding); float margin = res.getDimension(R.dimen.diff_line_separator_width) * 2; float blameWidth = res.getDimension(R.dimen.diff_line_blame_width); mDiffViewMeasurement.clear(); for (AbstractModel model : mModel) { if (model instanceof DiffInfoModel) { measureDiffInfoModel((DiffInfoModel) model, wrap, paint, padding, margin); } } // Give line number a minimum width mDiffViewMeasurement.lineNumWidth = Math.max(mDiffViewMeasurement.lineNumWidth, res.getDimension(R.dimen.diff_line_number_min_width)); // Blame float blame = (mShowBlameA ? blameWidth : 0) + (mShowBlameB ? blameWidth : 0); // Adjust padding mDiffViewMeasurement.lineNumWidth += (padding * 2f); float diffIndicatorWidth = mMode == UNIFIED_MODE ? res.getDimension(R.dimen.diff_line_indicator_width) : 0; float separatorWidth = (mMode == UNIFIED_MODE ? 2 : 3) * res.getDimension(R.dimen.diff_line_separator_width); float decorWidth = 2 * mDiffViewMeasurement.lineNumWidth + diffIndicatorWidth + separatorWidth; mDiffViewMeasurement.width = decorWidth + blame + (mMode == UNIFIED_MODE ? 1 : 2) * mDiffViewMeasurement.lineWidth; if (mDiffViewMeasurement.width < getWidth()) { mDiffViewMeasurement.width = getWidth(); if (mMode == UNIFIED_MODE) { mDiffViewMeasurement.lineWidth = getWidth() - decorWidth; } else { mDiffViewMeasurement.lineWidth = (getWidth() - decorWidth) / 2; } } // Update prefetch width to improve performance if (mLayoutManager instanceof UnwrappedLinearLayoutManager) { ((UnwrappedLinearLayoutManager) mLayoutManager) .setPrefetchedMeasuredWidth((int) Math.ceil(mDiffViewMeasurement.width)); } } else { // Remove prefetched width if (mLayoutManager instanceof UnwrappedLinearLayoutManager) { ((UnwrappedLinearLayoutManager) mLayoutManager).setPrefetchedMeasuredWidth(-1); } } } private void measureDiffInfoModel(DiffInfoModel diff, boolean wrap, TextPaint paint, float padding, float margin) { if (wrap) { mDiffViewMeasurement.lineWidth = MATCH_PARENT; } else { if (mMode == UNIFIED_MODE) { // All lines are displayed in A CharSequence line = diff.lineA != null ? diff.lineA : diff.lineB; mDiffViewMeasurement.lineWidth = Math.max(mDiffViewMeasurement.lineWidth, paint.measureText(String.valueOf(line)) + padding + margin); } else { // Lines are displayed in A and B and both have the same size if (diff.lineA != null) { String lineA = String.valueOf(diff.lineA); mDiffViewMeasurement.lineWidth = Math.max(mDiffViewMeasurement.lineWidth, paint.measureText(lineA) + padding + margin); } if (diff.lineB != null) { String lineB = String.valueOf(diff.lineB); mDiffViewMeasurement.lineWidth = Math.max(mDiffViewMeasurement.lineWidth, paint.measureText(lineB) + padding + margin); } } } if (diff.lineNumberA != null) { mDiffViewMeasurement.lineNumWidth = Math.max(mDiffViewMeasurement.lineNumWidth, paint.measureText(diff.lineNumberA)); } if (diff.lineNumberB != null) { mDiffViewMeasurement.lineNumWidth = Math.max(mDiffViewMeasurement.lineNumWidth, paint.measureText(diff.lineNumberB)); } } } private OnTextDiffProcessEndedListener mTextProcessorListener = new OnTextDiffProcessEndedListener() { @Override public void onTextDiffProcessEnded(List<AbstractModel> model) { if (mNeedsNewLayoutManager || !mLayoutManager.equals(mTmpLayoutManager)) { mDiffAdapter = new DiffView.DiffAdapter(mDiffMode); if (mTmpLayoutManager != null) { mLayoutManager = mTmpLayoutManager; } mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(mDiffAdapter); mNeedsNewLayoutManager = false; } mDiffAdapter.update(model, mPendingSkipLinesOpHistory); mTmpLayoutManager = null; // Should scroll? if (mPendingScrollToPosition != -1) { performScrollToPosition(); } else if (mPendingScrollToComment != null) { performScrollToComment(model); } mPendingScrollToPosition = -1; mPendingScrollToComment = null; mPendingSkipLinesOpHistory = null; mBinding.setProcessing(false); mBinding.executePendingBindings(); } }; private OnImageDiffProcessEndedListener mImageProcessorListener = new OnImageDiffProcessEndedListener() { @Override public void onImageDiffProcessEnded(ImageDiffModel model) { mBinding.setImageDiffModel(model); mBinding.executePendingBindings(); mPendingScrollToComment = null; mPendingScrollToPosition = -1; mBinding.setProcessing(false); mBinding.executePendingBindings(); } }; private DiffViewBinding mBinding; private final RecyclerView mRecyclerView; private DiffAdapter mDiffAdapter; private LinearLayoutManager mLayoutManager; private LinearLayoutManager mTmpLayoutManager; private String mFile; private boolean mHighlightTabs; private boolean mHighlightTrailingWhitespaces; private boolean mHighlightIntralineDiffs; private boolean mCanEdit; private int mDiffMode = UNIFIED_MODE; private float mTextSizeFactor = Constants.DEFAULT_TEXT_SIZE_NORMAL; private DiffInfo mDiffInfo; private Pair<List<CommentInfo>, List<CommentInfo>> mComments; private Pair<List<CommentInfo>, List<CommentInfo>> mDrafts; private Pair<List<BlameInfo>, List<BlameInfo>> mBlames; private File mLeftContent; private File mRightContent; private OnCommentListener mOnCommentListener; private boolean mShowBlameA; private boolean mShowBlameB; private boolean mNeedsNewLayoutManager; private EventHandlers mEventHandlers; private AsyncTextDiffProcessor mTextDiffTask; private AsyncImageDiffProcessor mImageDiffTask; private int mPendingScrollToPosition = -1; private String mPendingScrollToComment; private List<SkipLinesOpHistory> mPendingSkipLinesOpHistory; public DiffView(Context context) { this(context, null); } public DiffView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DiffView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mEventHandlers = new EventHandlers(this); mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.diff_view, this, false); mRecyclerView = mBinding.diffList; mLayoutManager = new LinearLayoutManager(context); mRecyclerView.setLayoutManager(mLayoutManager); mDiffAdapter = new DiffAdapter(mDiffMode); mRecyclerView.setAdapter(mDiffAdapter); mRecyclerView.setVerticalScrollBarEnabled(true); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); addView(mBinding.getRoot(), params); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mBinding.unbind(); // Stops running things now stopTasks(); } @Override protected Parcelable onSaveInstanceState() { SavedState savedState = new SavedState(super.onSaveInstanceState()); savedState.mFile = mFile; savedState.mPosition = mLayoutManager != null ? mLayoutManager.findFirstVisibleItemPosition() : -1; savedState.mHighlightTabs = mHighlightTabs; savedState.mHighlightTrailingWhitespaces = mHighlightTrailingWhitespaces; savedState.mHighlightIntralineDiffs = mHighlightIntralineDiffs; savedState.mCanEdit = mCanEdit; savedState.mDiffMode = mDiffMode; savedState.mDrafts = SerializationManager.getInstance().toJson(mDrafts); savedState.mShowBlameA = mShowBlameA; savedState.mShowBlameB = mShowBlameB; return savedState; } @Override protected void onRestoreInstanceState(Parcelable state) { //begin boilerplate code so parent classes can restore state if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); mFile = savedState.mFile; mPendingScrollToPosition = savedState.mPosition; mHighlightTabs = savedState.mHighlightTabs; mHighlightTrailingWhitespaces = savedState.mHighlightTrailingWhitespaces; mHighlightIntralineDiffs = savedState.mHighlightIntralineDiffs; mCanEdit = savedState.mCanEdit; mDiffMode = savedState.mDiffMode; Type type = new TypeToken<Pair<List<CommentInfo>, List<CommentInfo>>>() { }.getType(); mDrafts = SerializationManager.getInstance().fromJson(savedState.mDrafts, type); mShowBlameA = savedState.mShowBlameA; mShowBlameB = savedState.mShowBlameB; } public DiffView file(String file) { if (!file.equals(mFile)) { mPendingScrollToPosition = -1; } mFile = file; return this; } public DiffView from(DiffInfo diff) { mDiffInfo = diff; return this; } public DiffView withComments(Pair<List<CommentInfo>, List<CommentInfo>> comments) { mComments = comments; return this; } public DiffView withDrafts(Pair<List<CommentInfo>, List<CommentInfo>> drafts) { mDrafts = drafts; return this; } public DiffView withLeftContent(File path) { mLeftContent = path; return this; } public DiffView withRightContent(File path) { mRightContent = path; return this; } public DiffView withBlames(Pair<List<BlameInfo>, List<BlameInfo>> blames) { mBlames = blames; return this; } public DiffView canEdit(boolean canEdit) { mCanEdit = canEdit; return this; } public DiffView highlightTabs(boolean highlight) { mHighlightTabs = highlight; return this; } public DiffView highlightTrailingWhitespaces(boolean highlight) { mHighlightTrailingWhitespaces = highlight; return this; } public DiffView highlightIntralineDiffs(boolean highlight) { mHighlightIntralineDiffs = highlight; return this; } public DiffView showBlameA(boolean show) { mShowBlameA = show; return this; } public DiffView showBlameB(boolean show) { mShowBlameB = show; return this; } public DiffView wrap(boolean wrap) { if (isWrapMode() != wrap) { mTmpLayoutManager = wrap ? new LinearLayoutManager(getContext()) : new UnwrappedLinearLayoutManager(getContext()); } else { mTmpLayoutManager = mLayoutManager; } return this; } public DiffView textSizeFactor(float textSizeFactor) { mTextSizeFactor = textSizeFactor; return this; } public DiffView mode(int mode) { if (mDiffMode != mode) { mDiffMode = mode; mNeedsNewLayoutManager = true; mBinding.setMode(mode); } return this; } public DiffView listenOn(OnCommentListener cb) { mOnCommentListener = cb; return this; } public DiffView scrollToComment(String comment) { mPendingScrollToComment = comment; if (comment != null) { mPendingScrollToPosition = -1; } return this; } public DiffView scrollToPosition(int position) { mPendingScrollToPosition = position; return this; } public DiffView withSkipLinesHistory(String skipLinesHistory) { if (skipLinesHistory != null) { Type type = new TypeToken<ArrayList<DiffView.SkipLinesOpHistory>>() { }.getType(); mPendingSkipLinesOpHistory = SerializationManager.getInstance().fromJson(skipLinesHistory, type); } return this; } public void update() { stopTasks(); if (mDiffMode != IMAGE_MODE) { mTextDiffTask = new AsyncTextDiffProcessor(getContext(), mDiffMode, mDiffInfo, mComments, mDrafts, mBlames, mHighlightTabs, mHighlightTrailingWhitespaces, mHighlightIntralineDiffs, mTextProcessorListener); mTextDiffTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { mImageDiffTask = new AsyncImageDiffProcessor(getContext(), mLeftContent, mRightContent, mImageProcessorListener); mImageDiffTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } mBinding.setProcessing(true); mBinding.executePendingBindings(); } public void refresh() { if (mDiffAdapter != null) { mDiffAdapter.refresh(); } } private boolean isWrapMode() { return mLayoutManager == null || !(mLayoutManager instanceof UnwrappedLinearLayoutManager); } private void stopTasks() { // Stops running things now if (mTextDiffTask != null) { mTextDiffTask.cancel(true); } if (mImageDiffTask != null) { mImageDiffTask.cancel(true); } } private void onSkipLinePressed(int position) { mDiffAdapter.showSkippedLinesAt(position); mDiffAdapter.refresh(); } private void onSkipUpLinePressed(int position) { mDiffAdapter.showSkippedUpLinesAt(position); mDiffAdapter.refresh(); } private void onSkipDownLinePressed(int position) { mDiffAdapter.showSkippedDownLinesAt(position); mDiffAdapter.refresh(); } private void performScrollToPosition() { mLayoutManager.scrollToPosition(mPendingScrollToPosition); } private void performScrollToComment(List<AbstractModel> model) { // Compute comment position int position = -1; for (int i = 0; i < model.size(); i++) { AbstractModel am = model.get(i); if (am instanceof CommentModel) { CommentModel comment = (CommentModel) am; if (comment.commentA != null && comment.commentA.id.equals(mPendingScrollToComment)) { position = i; break; } if (comment.commentB != null && comment.commentB.id.equals(mPendingScrollToComment)) { position = i; break; } } } // Jump to position? if (position != -1) { int start = mLayoutManager.findFirstVisibleItemPosition(); int end = mLayoutManager.findLastVisibleItemPosition(); int index = position - (end - start + 1); if (index < 0) { index = 0; } else if (index >= mDiffAdapter.getItemCount()) { index = mDiffAdapter.getItemCount() - 1; } mLayoutManager.scrollToPosition(index); } } public int getScrollPosition() { return mLayoutManager.findFirstVisibleItemPosition(); } public String getSkipLinesHistory() { return SerializationManager.getInstance().toJson(mDiffAdapter.mSkipLinesOpHistory); } static class SavedState extends BaseSavedState { String mFile; int mPosition = -1; boolean mHighlightTabs; boolean mHighlightTrailingWhitespaces; boolean mHighlightIntralineDiffs; boolean mCanEdit; int mDiffMode; String mDrafts; boolean mShowBlameA; boolean mShowBlameB; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); mFile = in.readString(); mPosition = in.readInt(); mHighlightTabs = in.readInt() == 1; mHighlightTrailingWhitespaces = in.readInt() == 1; mHighlightIntralineDiffs = in.readInt() == 1; mCanEdit = in.readInt() == 1; mDiffMode = in.readInt(); mDrafts = in.readString(); mShowBlameA = in.readInt() == 1; mShowBlameB = in.readInt() == 1; } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeString(mFile); out.writeInt(mPosition); out.writeInt(mHighlightTabs ? 1 : 0); out.writeInt(mHighlightTrailingWhitespaces ? 1 : 0); out.writeInt(mHighlightIntralineDiffs ? 1 : 0); out.writeInt(mCanEdit ? 1 : 0); out.writeInt(mDiffMode); out.writeString(mDrafts); out.writeInt(mShowBlameA ? 1 : 0); out.writeInt(mShowBlameB ? 1 : 0); } //required field that makes Parcelables from a Parcel public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }