package com.alorma.github.ui.fragment.pullrequest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.alorma.github.GitskariosSettings;
import com.alorma.github.R;
import com.alorma.github.StoreCredentials;
import com.alorma.github.sdk.bean.dto.request.EditIssueBodyRequestDTO;
import com.alorma.github.sdk.bean.dto.request.EditIssueRequestDTO;
import com.alorma.github.sdk.bean.dto.request.EditIssueTitleRequestDTO;
import com.alorma.github.sdk.bean.dto.request.MergeButtonRequest;
import com.alorma.github.sdk.bean.dto.request.UpdateReferenceRequest;
import com.alorma.github.sdk.bean.dto.response.Head;
import com.alorma.github.sdk.bean.dto.response.Issue;
import com.alorma.github.sdk.bean.dto.response.IssueState;
import com.alorma.github.sdk.bean.dto.response.MergeButtonResponse;
import com.alorma.github.sdk.bean.info.IssueInfo;
import com.alorma.github.sdk.bean.info.RepoInfo;
import com.alorma.github.sdk.bean.issue.IssueStoryComment;
import com.alorma.github.sdk.bean.issue.PullRequestStory;
import com.alorma.github.sdk.services.issues.EditIssueClient;
import com.alorma.github.sdk.services.pullrequest.MergePullRequestClient;
import com.alorma.github.sdk.services.pullrequest.story.PullRequestStoryLoader;
import com.alorma.github.sdk.services.reference.DeleteReferenceClient;
import com.alorma.github.sdk.services.reference.GetReferenceClient;
import com.alorma.github.sdk.services.repo.GetRepoClient;
import com.alorma.github.ui.ErrorHandler;
import com.alorma.github.ui.actions.AddIssueCommentAction;
import com.alorma.github.ui.activity.ContentEditorActivity;
import com.alorma.github.ui.adapter.issues.PullRequestDetailAdapter;
import com.alorma.github.ui.fragment.base.BaseFragment;
import com.alorma.github.ui.listeners.IssueCommentRequestListener;
import com.alorma.github.ui.listeners.IssueDetailRequestListener;
import com.alorma.github.ui.utils.DialogUtils;
import com.alorma.github.ui.view.pullrequest.PullRequestDetailView;
import com.alorma.github.utils.IssueUtils;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.octicons_typeface_library.Octicons;
import core.repositories.Repo;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class PullRequestConversationFragment extends BaseFragment
implements PullRequestDetailView.PullRequestActionsListener, IssueDetailRequestListener, SwipeRefreshLayout.OnRefreshListener,
IssueCommentRequestListener {
public static final String ISSUE_INFO = "ISSUE_INFO";
public static final String ISSUE_INFO_REPO_NAME = "ISSUE_INFO_REPO_NAME";
public static final String ISSUE_INFO_REPO_OWNER = "ISSUE_INFO_REPO_OWNER";
public static final String ISSUE_INFO_NUMBER = "ISSUE_INFO_NUMBER";
private static final int NEW_COMMENT_REQUEST = 1243;
private static final int ISSUE_BODY_EDIT = 4252;
private IssueInfo issueInfo;
private SwipeRefreshLayout swipe;
private RecyclerView recyclerView;
private PullRequestStory pullRequestStory;
private Repo repository;
private PullRequestStoryLoaderInterface pullRequestStoryLoaderInterfaceNull = story -> {
};
private PullRequestStoryLoaderInterface pullRequestStoryLoaderInterface = pullRequestStoryLoaderInterfaceNull;
private PullRequestDetailAdapter adapter;
private boolean headReferenceExist = false;
private boolean hasPushPermissionsToHead = false;
public static PullRequestConversationFragment newInstance(IssueInfo issueInfo) {
Bundle bundle = new Bundle();
bundle.putParcelable(ISSUE_INFO, issueInfo);
PullRequestConversationFragment fragment = new PullRequestConversationFragment();
fragment.setArguments(bundle);
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.pullrequest_detail_fragment, null, false);
}
@Override
protected int getLightTheme() {
return R.style.AppTheme_Repository;
}
@Override
protected int getDarkTheme() {
return R.style.AppTheme_Dark_Repository;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
findViews(view);
if (getArguments() != null) {
issueInfo = getArguments().getParcelable(ISSUE_INFO);
if (issueInfo == null && getArguments().containsKey(ISSUE_INFO_NUMBER)) {
String name = getArguments().getString(ISSUE_INFO_REPO_NAME);
String owner = getArguments().getString(ISSUE_INFO_REPO_OWNER);
RepoInfo repoInfo = new RepoInfo();
repoInfo.name = name;
repoInfo.owner = owner;
int num = getArguments().getInt(ISSUE_INFO_NUMBER);
issueInfo = new IssueInfo();
issueInfo.repoInfo = repoInfo;
issueInfo.num = num;
}
setHasOptionsMenu(true);
getContent();
}
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (pullRequestStory != null && new IssueUtils().canComment(pullRequestStory.item)) {
getActivity().getMenuInflater().inflate(R.menu.pullrequest_detail_overview, menu);
MenuItem item = menu.findItem(R.id.action_pull_request_add_comment);
if (item != null) {
item.setIcon(new IconicsDrawable(getActivity()).icon(Octicons.Icon.oct_comment_add).actionBar().color(Color.WHITE));
}
} else {
menu.removeItem(R.id.action_pull_request_add_comment);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_pull_request_add_comment) {
onAddComment();
}
return super.onOptionsItemSelected(item);
}
private void onAddComment() {
String hint = getString(R.string.add_comment);
Intent intent = ContentEditorActivity.createLauncherIntent(getActivity(), issueInfo.repoInfo, issueInfo.num, hint, null, false, false);
startActivityForResult(intent, NEW_COMMENT_REQUEST);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && data != null) {
if (requestCode == NEW_COMMENT_REQUEST) {
final String body = data.getStringExtra(ContentEditorActivity.CONTENT);
AddIssueCommentAction addIssueCommentAction = getAddIssueCommentAction(body);
addIssueCommentAction.setAddCommentCallback(new CommentCallback());
addIssueCommentAction.execute();
} else if (requestCode == ISSUE_BODY_EDIT) {
EditIssueBodyRequestDTO bodyRequestDTO = new EditIssueBodyRequestDTO();
bodyRequestDTO.body = data.getStringExtra(ContentEditorActivity.CONTENT);
executeEditIssue(bodyRequestDTO);
}
}
}
public void setPullRequestStoryLoaderInterface(PullRequestStoryLoaderInterface pullRequestStoryLoaderInterface) {
this.pullRequestStoryLoaderInterface = pullRequestStoryLoaderInterface;
}
@NonNull
private AddIssueCommentAction getAddIssueCommentAction(String body) {
return new AddIssueCommentAction(issueInfo, body);
}
private void checkEditTitle() {
if (getActivity() != null) {
if (issueInfo != null && pullRequestStoryItemExist()) {
StoreCredentials credentials = new StoreCredentials(getActivity());
GitskariosSettings settings = new GitskariosSettings(getActivity());
if (settings.shouldShowDialogEditIssue()) {
if (issueInfo.repoInfo.permissions != null && issueInfo.repoInfo.permissions.push) {
showEditDialog(R.string.dialog_edit_issue_edit_title_and_body_by_owner);
} else if (pullRequestStory.item.user.getLogin().equals(credentials.getUserName())) {
showEditDialog(R.string.dialog_edit_issue_edit_title_and_body_by_author);
}
}
}
}
}
private void showEditDialog(int content) {
new DialogUtils().builder(getActivity()).title(R.string.dialog_edit_issue).content(content).positiveText(R.string.ok).show();
}
private void findViews(View rootView) {
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
swipe = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe);
if (swipe != null) {
swipe.setColorSchemeResources(R.color.accent);
}
}
private void getContent() {
if (pullRequestStory == null) {
GetRepoClient repoClient = new GetRepoClient(issueInfo.repoInfo);
repoClient.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Repo>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Repo repo) {
issueInfo.repoInfo.permissions = repo.permissions;
repository = repo;
loadPullRequest();
}
});
} else {
onResponseOk(pullRequestStory);
}
}
private void loadPullRequest() {
PullRequestStoryLoader pullRequestStoryLoader = new PullRequestStoryLoader(issueInfo);
pullRequestStoryLoader.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(story -> {
checkHeadBranchExist(story);
onResponseOk(story);
}, this::showError);
}
private void showError(Throwable throwable) {
MaterialDialog.Builder builder = new DialogUtils().builder(getActivity());
builder.title(R.string.ups);
builder.content(getString(R.string.issue_detail_error, issueInfo.toString()));
builder.positiveText(R.string.retry);
builder.negativeText(R.string.accept);
builder.onPositive((dialog, which) -> getContent());
builder.onNegative((dialog, which) -> getActivity().finish());
builder.show();
}
public void onResponseOk(final PullRequestStory pullRequestStory) {
if (getActivity() != null) {
getActivity().invalidateOptionsMenu();
this.pullRequestStory = pullRequestStory;
this.pullRequestStory.item.repository = repository;
if (pullRequestStoryLoaderInterface != null) {
pullRequestStoryLoaderInterface.onStoryLoaded(pullRequestStory);
}
swipe.setRefreshing(false);
swipe.setOnRefreshListener(this);
applyIssue();
}
}
private void checkHeadBranchExist(PullRequestStory pullRequestStory) {
Head head = pullRequestStory.item.head;
if (head.repo != null) {
RepoInfo headRepoInfo = head.repo.toInfo();
GetRepoClient getRepoClient = new GetRepoClient(headRepoInfo);
GetReferenceClient referenceClient = new GetReferenceClient(headRepoInfo, head.ref);
getRepoClient.observable()
.flatMap((repo) -> {
if (repo.permissions != null) {
hasPushPermissionsToHead = repo.permissions.push;
}
return referenceClient.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(gitReference -> notifyHeadOfAdapter(true), throwable -> notifyHeadOfAdapter(false));
} else {
hasPushPermissionsToHead = false;
notifyHeadOfAdapter(false);
}
}
private void notifyHeadOfAdapter(boolean headReferenceExist) {
// refresh head only if pull request is closed or merged
if (pullRequestStoryItemExist() && pullRequestClosedOrMerged()) {
this.headReferenceExist = headReferenceExist;
adapter.notifyItemChanged(0);
}
}
private boolean pullRequestClosedOrMerged() {
return pullRequestStory.item.state == IssueState.closed || pullRequestStory.item.merged;
}
private boolean pullRequestStoryItemExist() {
return pullRequestStory != null && pullRequestStory.item != null;
}
private void applyIssue() {
checkEditTitle();
// TODO changeColor(pullRequestStory.item);
String status = getString(R.string.issue_status_open);
if (IssueState.closed == pullRequestStory.item.state) {
status = getString(R.string.issue_status_close);
} else if (pullRequestStory.item.merged) {
status = getString(R.string.pullrequest_status_merged);
}
getActivity().setTitle("#" + pullRequestStory.item.number + " " + status);
adapter =
new PullRequestDetailAdapter(getActivity(), getActivity().getLayoutInflater(), pullRequestStory, issueInfo.repoInfo, this, this);
recyclerView.setAdapter(adapter);
getActivity().invalidateOptionsMenu();
}
@Override
public void onRefresh() {
getContent();
swipe.setOnRefreshListener(null);
}
@Override
public void onTitleEditRequest() {
new DialogUtils().builder(getActivity())
.title(R.string.edit_issue_title)
.input(null, pullRequestStory.item.title, false, (materialDialog, charSequence) -> {
EditIssueTitleRequestDTO editIssueTitleRequestDTO = new EditIssueTitleRequestDTO();
editIssueTitleRequestDTO.title = charSequence.toString();
executeEditIssue(editIssueTitleRequestDTO);
})
.positiveText(R.string.edit_issue_button_ok)
.neutralText(R.string.edit_issue_button_neutral)
.show();
}
@Override
public void onContentEditRequest() {
String body = pullRequestStory.item.body != null ? pullRequestStory.item.body.replace("\n", "<br />") : "";
Intent launcherIntent = ContentEditorActivity.createLauncherIntent(getActivity(), issueInfo.repoInfo, issueInfo.num,
getString(R.string.edit_issue_body_hint), body, true, false);
startActivityForResult(launcherIntent, ISSUE_BODY_EDIT);
}
private void executeEditIssue(EditIssueRequestDTO editIssueRequestDTO) {
EditIssueClient client = new EditIssueClient(issueInfo, editIssueRequestDTO);
client.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Issue>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
ErrorHandler.onError(getActivity(), "Issue detail", e);
}
@Override
public void onNext(Issue issue) {
getContent();
}
});
}
@Override
public void mergeRequest(Head head, Head base) {
MaterialDialog.Builder builder = new DialogUtils().builder(getActivity());
builder.title(R.string.merge_title);
builder.content(head.label);
builder.input(getString(R.string.merge_message), pullRequestStory.item.title, false, (materialDialog, charSequence) -> {
merge(charSequence.toString(), head.sha, issueInfo);
});
builder.inputType(InputType.TYPE_CLASS_TEXT);
dialog = builder.show();
}
@Override
public boolean userIsAbleToDelete() {
return headReferenceExist && hasPushPermissionsToHead;
}
@Override
public void deleteHeadReference(Head head) {
MaterialDialog.Builder builder = new DialogUtils().builder(getActivity());
builder.title(R.string.pull_request_delete_branch_question);
builder.content(head.ref);
builder.positiveText(R.string.ok);
builder.negativeText(R.string.cancel);
builder.onPositive((dialog1, which) -> {
callDeleteHeadReference(head);
});
builder.onNegative((dialog1, which) -> {
dialog1.dismiss();
});
dialog = builder.show();
}
private void callDeleteHeadReference(Head head) {
UpdateReferenceRequest updateReferenceRequest = new UpdateReferenceRequest();
updateReferenceRequest.sha = head.sha;
updateReferenceRequest.force = true;
String ref = head.ref;
DeleteReferenceClient deleteReferenceClient = new DeleteReferenceClient(head.repo.toInfo(), ref);
deleteReferenceClient.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe((result) -> {
if (result) {
restartActivity();
} else {
Toast.makeText(getActivity(), "Failed to delete branch.", Toast.LENGTH_SHORT).show();
}
}, (throwable) -> {
Toast.makeText(getActivity(), "Failed to delete branch.", Toast.LENGTH_SHORT).show();
});
}
private void merge(String message, String sha, IssueInfo issueInfo) {
MergeButtonRequest mergeButtonRequest = new MergeButtonRequest();
mergeButtonRequest.commit_message = message;
mergeButtonRequest.sha = sha;
MergePullRequestClient mergePullRequestClient = new MergePullRequestClient(issueInfo, mergeButtonRequest);
mergePullRequestClient.observable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MergeButtonResponse>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(MergeButtonResponse mergeButtonResponse) {
restartActivity();
}
});
}
private void restartActivity() {
startActivity(getActivity().getIntent());
getActivity().finish();
}
@Override
public void onContentEditRequest(IssueStoryComment issueStoryComment) {
if (getActivity() != null) {
Toast.makeText(getActivity(), "Not ready yet", Toast.LENGTH_SHORT).show();
}
}
public interface PullRequestStoryLoaderInterface {
void onStoryLoaded(PullRequestStory story);
}
private class CommentCallback implements AddIssueCommentAction.AddCommentCallback {
private ProgressDialog progressDialog;
private CommentCallback() {
}
@Override
public void onCommentAdded() {
if (progressDialog != null) {
progressDialog.dismiss();
}
pullRequestStory = null;
getContent();
}
@Override
public void onCommentError() {
if (progressDialog != null) {
progressDialog.dismiss();
}
}
@Override
public void onCommentAddStarted() {
progressDialog = new ProgressDialog(getActivity());
progressDialog.setMessage(getString(R.string.adding_comment));
progressDialog.setCancelable(true);
progressDialog.show();
}
}
}