blob: f99d8128952b7fae20c13f4e161e7d6afb65c1bc [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// 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.google.reviewit;
import android.content.Intent;
import android.graphics.Paint;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.text.util.Linkify;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.reviewit.app.ActionHandler;
import com.google.reviewit.app.Change;
import com.google.reviewit.util.ChangeUtil;
import com.google.reviewit.widget.ChangeBox;
import com.google.reviewit.util.FormatUtil;
import com.google.reviewit.util.TaskObserver;
import com.google.reviewit.util.WidgetUtil;
import com.google.reviewit.widget.ApprovalsView;
import com.google.reviewit.widget.ZoomHandler;
import java.util.Map;
import java.util.regex.Pattern;
import static com.google.reviewit.util.LayoutUtil.matchAndWrapTableLayout;
import static com.google.reviewit.util.LayoutUtil.matchAndWrapTableRowLayout;
import static com.google.reviewit.util.WidgetUtil.setGone;
import static com.google.reviewit.util.WidgetUtil.setVisible;
/**
* Fragment to show details of a change.
*/
public class DetailedChangeFragment extends BaseFragment implements
OnBackPressedAware, DispatchTouchEventAware {
private static final String TAG = DetailedChangeFragment.class.getName();
private static final int PAGE_SIZE = 10;
private static final Pattern PATTERN_CHANGE_ID =
Pattern.compile("I[0-9a-f]{5,40}");
private static final String PART_LINK = "(?:"
+ "[a-zA-Z0-9$_+!*'%;:@=?#/~-]"
+ "|&(?!lt;|gt;)"
+ "|[.,](?!(?:\\s|$))"
+ ")";
private static final Pattern PATTERN_LINK = Pattern.compile(
"https?://"
+ PART_LINK + "{2,}"
+ "(?:[(]" + PART_LINK + "*" + "[)])*"
+ PART_LINK + "*");
private static final Pattern PATTERN_EMAIL =
Pattern.compile("[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}",
Pattern.CASE_INSENSITIVE);
private ZoomHandler zoomHandler;
@Override
protected @LayoutRes int getLayout() {
return R.layout.content_detailed_change;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Change change = getApp().getActionHandler().getCurrentChange();
setTitle(getString(R.string.detailed_change_title, change.info._number));
setHasOptionsMenu(true);
init();
zoomHandler = new ZoomHandler(v(R.id.scrollContent));
TaskObserver.enableProgressBar(getWindow());
try {
ChangeUtil.colorBackground(root, change);
((ChangeBox) v(R.id.changeBox)).display(getApp(), change);
linkify();
displayChangeUrl(change);
((ApprovalsView) v(R.id.approvals)).displayApprovals(getApp(),
change.info, this);
displayFiles(change, 1, false);
// TODO show further change info, e.g. summary comments, hashtags,
// related changes
} catch (Throwable t) {
Log.e(TAG, "Failed to display change", t);
display(ErrorFragment.create(t));
}
}
@Override
public void dispatchTouchEvent(MotionEvent event) {
zoomHandler.dispatchTouchEvent(event);
}
private void init() {
TextView commitMsg = (TextView) v(R.id.commitMessage);
commitMsg.setLinksClickable(true);
v(R.id.reviewButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
display(UnifiedDiffFragment.class);
}
});
WidgetUtil.underline(tv(R.id.showMore));
WidgetUtil.underline(tv(R.id.showAll));
}
private void linkify() {
TextView commitMsg = tv(R.id.commitMessage);
Linkify.addLinks(commitMsg, PATTERN_CHANGE_ID, getServerUrl() + "#/q/");
Linkify.addLinks(commitMsg, PATTERN_LINK, "");
Linkify.addLinks(commitMsg, PATTERN_EMAIL, "");
}
private void displayChangeUrl(Change change) {
WidgetUtil.setText(v(R.id.changeUrl), change.getUrl(getServerUrl()));
setVisible(v(R.id.changeUrlBox));
}
private String getServerUrl() {
String serverUrl = getApp().getConfigManager().getServerConfig().url;
return FormatUtil.ensureSlash(serverUrl);
}
private void displayFiles(
final Change change, final int page, boolean showAll) {
TableLayout tl = (TableLayout) v(R.id.filesTable);
Map<String, FileInfo> files = change.currentRevision().files;
int count = 0;
for (Map.Entry<String, FileInfo> e : files.entrySet()) {
count++;
if (count <= (page - 1) * PAGE_SIZE) {
continue;
}
if (!showAll && count > page * PAGE_SIZE) {
break;
}
addFileRow(tl, e.getKey(), e.getValue());
}
if (!showAll && files.size() > page * PAGE_SIZE) {
WidgetUtil.setText(v(R.id.showAll),
getString(R.string.show_all, files.size() - page * PAGE_SIZE));
setVisible(v(R.id.fileButtons));
v(R.id.showAll).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayFiles(change, page + 1, true);
}
});
if (files.size() > (page + 1) * PAGE_SIZE) {
WidgetUtil.setText(v(R.id.showMore),
getString(R.string.show_more, PAGE_SIZE));
setVisible(v(R.id.showMoreArea, R.id.showMore));
v(R.id.showMore).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayFiles(change, page + 1, false);
}
});
} else {
setGone(v(R.id.showMoreArea, R.id.showMore));
}
} else {
setGone(v(R.id.fileButtons));
}
}
private void addFileRow(TableLayout tl, final String path, FileInfo file) {
TableRow tr = new TableRow(getActivity());
tr.setLayoutParams(matchAndWrapTableRowLayout());
tr.addView(widgetUtil.tableRowRightMargin(widgetUtil.createTextView(
file.status != null ? Character.toString(file.status) : "M", 11), 4));
TextView pathText = widgetUtil.createTextView(path, 11);
pathText.setTextColor(widgetUtil.color(R.color.hyperlink));
pathText.setPaintFlags(
pathText.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
pathText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
display(UnifiedDiffFragment.create(path));
}
});
tr.addView(widgetUtil.tableRowRightMargin(pathText, 4));
tr.addView(widgetUtil.createTextView(
FormatUtil.formatBytes(file.size), 11));
// TODO show further file infos
tl.addView(tr, matchAndWrapTableLayout());
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
ActionHandler actionHandler = getApp().getActionHandler();
inflater.inflate(R.menu.menu_detailed_change, menu);
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
if (item.getItemId() == R.id.action_abandon) {
item.setVisible(actionHandler.hasCurrentChange()
&& (actionHandler.getCurrentChange().info.status
== ChangeStatus.NEW
|| actionHandler.getCurrentChange().info.status
== ChangeStatus.SUBMITTED));
} else if (item.getItemId() == R.id.action_restore) {
item.setVisible(actionHandler.hasCurrentChange()
&& actionHandler.getCurrentChange().info.status
== ChangeStatus.ABANDONED);
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
ActionHandler actionHandler = getApp().getActionHandler();
switch (item.getItemId()) {
case R.id.action_add_reviewer:
display(AddReviewerFragment.create(getClass()));
return true;
case R.id.action_abandon:
display(AbandonFragment.create(getClass()));
return true;
case R.id.action_help:
display(HelpFragment.class);
return true;
case R.id.action_ignore:
actionHandler.ignore();
startActivity(new Intent(getActivity(), MainActivity.class));
return true;
case R.id.action_restore:
display(RestoreFragment.create(getClass()));
return true;
case R.id.action_skip:
actionHandler.skip();
startActivity(new Intent(getActivity(), MainActivity.class));
return true;
case R.id.action_star:
actionHandler.star();
startActivity(new Intent(getActivity(), MainActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onBackPressed() {
display(SortChangesFragment.class);
return true;
}
}