/*
* Copyright 2011 Azwan Adli Abdullah
*
* 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.gh4a.activities;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.gh4a.BaseActivity;
import com.gh4a.Gh4Application;
import com.gh4a.ProgressDialogTask;
import com.gh4a.R;
import com.gh4a.loader.CollaboratorListLoader;
import com.gh4a.loader.IsCollaboratorLoader;
import com.gh4a.loader.IssueTemplateLoader;
import com.gh4a.loader.LabelListLoader;
import com.gh4a.loader.LoaderCallbacks;
import com.gh4a.loader.LoaderResult;
import com.gh4a.loader.MilestoneListLoader;
import com.gh4a.loader.ProgressDialogLoaderCallbacks;
import com.gh4a.utils.ApiHelpers;
import com.gh4a.utils.AvatarHandler;
import com.gh4a.utils.UiUtils;
import org.eclipse.egit.github.core.Issue;
import org.eclipse.egit.github.core.Label;
import org.eclipse.egit.github.core.Milestone;
import org.eclipse.egit.github.core.User;
import org.eclipse.egit.github.core.service.IssueService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class IssueEditActivity extends BaseActivity implements View.OnClickListener {
public static Intent makeCreateIntent(Context context, String repoOwner, String repoName) {
// can't reuse makeEditIntent here, because even a null extra counts for hasExtra()
return new Intent(context, IssueEditActivity.class)
.putExtra("owner", repoOwner)
.putExtra("repo", repoName);
}
public static Intent makeEditIntent(Context context, String repoOwner,
String repoName, Issue issue) {
return new Intent(context, IssueEditActivity.class)
.putExtra("owner", repoOwner)
.putExtra("repo", repoName)
.putExtra("issue", issue);
}
private static final int REQUEST_MANAGE_LABELS = 1000;
private static final int REQUEST_MANAGE_MILESTONES = 1001;
private String mRepoOwner;
private String mRepoName;
private boolean mIsCollaborator;
private List<Milestone> mAllMilestone;
private List<User> mAllAssignee;
private List<Label> mAllLabels;
private Issue mEditIssue;
private TextInputLayout mTitleWrapper;
private EditText mTitleView;
private TextInputLayout mDescWrapper;
private EditText mDescView;
private TextView mTvSelectedMilestone;
private TextView mTvSelectedAssignee;
private ViewGroup mSelectedAssigneeContainer;
private TextView mTvLabels;
private View mMilestoneContainer;
private View mAssigneeContainer;
private View mLabelContainer;
private static final String STATE_KEY_ISSUE = "issue";
private final LoaderCallbacks<List<Label>> mLabelCallback =
new ProgressDialogLoaderCallbacks<List<Label>>(this, this) {
@Override
protected Loader<LoaderResult<List<Label>>> onCreateLoader() {
return new LabelListLoader(IssueEditActivity.this, mRepoOwner, mRepoName);
}
@Override
protected void onResultReady(List<Label> result) {
mAllLabels = result;
showLabelDialog();
getSupportLoaderManager().destroyLoader(0);
}
};
private final LoaderCallbacks<List<Milestone>> mMilestoneCallback =
new ProgressDialogLoaderCallbacks<List<Milestone>>(this, this) {
@Override
protected Loader<LoaderResult<List<Milestone>>> onCreateLoader() {
return new MilestoneListLoader(IssueEditActivity.this,
mRepoOwner, mRepoName, ApiHelpers.IssueState.OPEN);
}
@Override
protected void onResultReady(List<Milestone> result) {
mAllMilestone = result;
showMilestonesDialog();
getSupportLoaderManager().destroyLoader(1);
}
};
private final LoaderCallbacks<List<User>> mCollaboratorListCallback =
new ProgressDialogLoaderCallbacks<List<User>>(this, this) {
@Override
protected Loader<LoaderResult<List<User>>> onCreateLoader() {
return new CollaboratorListLoader(IssueEditActivity.this, mRepoOwner, mRepoName);
}
@Override
protected void onResultReady(List<User> result) {
mAllAssignee = result;
showAssigneesDialog();
getSupportLoaderManager().destroyLoader(2);
}
};
private final LoaderCallbacks<Boolean> mIsCollaboratorCallback = new LoaderCallbacks<Boolean>(this) {
@Override
protected Loader<LoaderResult<Boolean>> onCreateLoader() {
return new IsCollaboratorLoader(IssueEditActivity.this, mRepoOwner, mRepoName);
}
@Override
protected void onResultReady(Boolean result) {
mIsCollaborator = result;
updateLabels();
updateLabelStates();
}
};
private final LoaderCallbacks<String> mIssueTemplateCallback = new LoaderCallbacks<String>(this) {
@Override
protected Loader<LoaderResult<String>> onCreateLoader() {
return new IssueTemplateLoader(IssueEditActivity.this, mRepoOwner, mRepoName);
}
@Override
protected void onResultReady(String result) {
getSupportLoaderManager().destroyLoader(4);
mDescWrapper.setHint(getString(R.string.issue_body));
mDescView.setEnabled(true);
mDescView.setText(result);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!Gh4Application.get().isAuthorized()) {
Intent intent = new Intent(this, Github4AndroidActivity.class);
startActivity(intent);
finish();
return;
}
setContentView(R.layout.issue_create);
setToolbarScrollable(false);
LayoutInflater headerInflater = LayoutInflater.from(UiUtils.makeHeaderThemedContext(this));
View header = headerInflater.inflate(R.layout.issue_create_header, null);
addHeaderView(header, false);
mTitleWrapper = (TextInputLayout) header.findViewById(R.id.title_wrapper);
mTitleView = (EditText) header.findViewById(R.id.et_title);
mDescWrapper = (TextInputLayout) header.findViewById(R.id.desc_wrapper);
mDescView = (EditText) header.findViewById(R.id.et_desc);
CoordinatorLayout rootLayout = getRootLayout();
FloatingActionButton fab = (FloatingActionButton)
getLayoutInflater().inflate(R.layout.accept_fab, rootLayout, false);
fab.setOnClickListener(this);
rootLayout.addView(fab);
findViewById(R.id.milestone_container).setOnClickListener(this);
findViewById(R.id.assignee_container).setOnClickListener(this);
findViewById(R.id.label_container).setOnClickListener(this);
mTvSelectedMilestone = (TextView) findViewById(R.id.tv_milestone);
mTvSelectedAssignee = (TextView) findViewById(R.id.tv_assignee);
mSelectedAssigneeContainer = (ViewGroup) findViewById(R.id.assignee_list);
mTvLabels = (TextView) findViewById(R.id.tv_labels);
mMilestoneContainer = findViewById(R.id.milestone_container);
mAssigneeContainer = findViewById(R.id.assignee_container);
mLabelContainer = findViewById(R.id.label_container);
getSupportLoaderManager().initLoader(3, null, mIsCollaboratorCallback);
if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_ISSUE)) {
mEditIssue = (Issue) savedInstanceState.getSerializable(STATE_KEY_ISSUE);
} else if (!isInEditMode()) {
getSupportLoaderManager().initLoader(4, null, mIssueTemplateCallback);
mDescView.setEnabled(false);
mDescWrapper.setHint(getString(R.string.issue_loading_template_hint));
}
if (mEditIssue == null) {
mEditIssue = new Issue();
}
mTitleView.setText(mEditIssue.getTitle());
mDescView.setText(mEditIssue.getBody());
mTitleView.addTextChangedListener(new UiUtils.ButtonEnableTextWatcher(mTitleView, fab));
mTitleView.addTextChangedListener(new UiUtils.EmptinessWatchingTextWatcher(mTitleView) {
@Override
public void onIsEmpty(boolean isEmpty) {
if (isEmpty) {
mTitleWrapper.setError(getString(R.string.issue_error_title));
} else {
mTitleWrapper.setErrorEnabled(false);
}
}
});
boolean isPullRequest = mEditIssue.getPullRequest() != null;
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(!isInEditMode()
? getString(R.string.issue_create)
: isPullRequest
? getString(R.string.pull_request_edit_title, mEditIssue.getNumber())
: getString(R.string.issue_edit_title, mEditIssue.getNumber()));
actionBar.setSubtitle(mRepoOwner + "/" + mRepoName);
actionBar.setDisplayHomeAsUpEnabled(true);
updateLabels();
updateLabelStates();
setToolbarScrollable(false);
}
@Override
protected void onInitExtras(Bundle extras) {
super.onInitExtras(extras);
mRepoOwner = extras.getString("owner");
mRepoName = extras.getString("repo");
mEditIssue = (Issue) extras.getSerializable("issue");
}
@Override
protected boolean canSwipeToRefresh() {
// swipe-to-refresh doesn't make much sense in the
// interaction model of this activity
return false;
}
@Override
public void onRefresh() {
mAllAssignee = null;
mAllMilestone = null;
mAllLabels = null;
mIsCollaborator = false;
updateLabels();
updateLabelStates();
forceLoaderReload(0, 1, 2, 3);
super.onRefresh();
}
private boolean isInEditMode() {
return getIntent().hasExtra("issue");
}
@Override
public void onClick(View view) {
int id = view.getId();
if (id == R.id.milestone_container) {
showMilestonesDialog();
} else if (id == R.id.assignee_container) {
showAssigneesDialog();
} else if (id == R.id.label_container) {
showLabelDialog();
} else if (view instanceof FloatingActionButton) {
mEditIssue.setTitle(mTitleView.getText().toString());
mEditIssue.setBody(mDescView.getText().toString());
new SaveIssueTask(mEditIssue).schedule();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mEditIssue != null) {
outState.putSerializable(STATE_KEY_ISSUE, mEditIssue);
}
}
@Override
protected Intent navigateUp() {
if (!isInEditMode()) {
return IssueListActivity.makeIntent(this, mRepoOwner, mRepoName);
}
if (mEditIssue.getPullRequest() != null) {
return PullRequestActivity.makeIntent(this, mRepoOwner, mRepoName,
mEditIssue.getNumber());
}
return IssueActivity.makeIntent(this, mRepoOwner, mRepoName, mEditIssue.getNumber());
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_MANAGE_LABELS) {
if (resultCode == RESULT_OK) {
// Require reload of labels
mAllLabels = null;
}
} else if (requestCode == REQUEST_MANAGE_MILESTONES) {
if (resultCode == RESULT_OK) {
// Require reload of milestones
mAllMilestone = null;
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void showMilestonesDialog() {
if (mAllMilestone == null) {
getSupportLoaderManager().initLoader(1, null, mMilestoneCallback);
} else {
final String[] milestones = new String[mAllMilestone.size() + 1];
Milestone selectedMilestone = mEditIssue.getMilestone();
int selected = 0;
milestones[0] = getResources().getString(R.string.issue_clear_milestone);
for (int i = 1; i <= mAllMilestone.size(); i++) {
Milestone m = mAllMilestone.get(i - 1);
milestones[i] = m.getTitle();
if (selectedMilestone != null && m.getNumber() == selectedMilestone.getNumber()) {
selected = i;
}
}
final DialogInterface.OnClickListener selectCb = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
mEditIssue.setMilestone(null);
} else {
mEditIssue.setMilestone(mAllMilestone.get(which - 1));
}
updateLabels();
dialog.dismiss();
}
};
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(R.string.issue_milestone_hint)
.setSingleChoiceItems(milestones, selected, selectCb)
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.issue_manage_milestones,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = IssueMilestoneListActivity.makeIntent(
IssueEditActivity.this, mRepoOwner, mRepoName,
mEditIssue.getPullRequest() != null);
startActivityForResult(intent, REQUEST_MANAGE_MILESTONES);
}
})
.show();
}
}
private void showAssigneesDialog() {
if (mAllAssignee == null) {
getSupportLoaderManager().initLoader(2, null, mCollaboratorListCallback);
} else {
final String[] assigneeNames = new String[mAllAssignee.size()];
final boolean[] selection = new boolean[mAllAssignee.size()];
final List<User> oldAssigneeList = mEditIssue.getAssignees() != null
? mEditIssue.getAssignees() : new ArrayList<User>();
List<String> assigneeLogins = new ArrayList<>();
for (User assignee : oldAssigneeList) {
assigneeLogins.add(assignee.getLogin());
}
for (int i = 0; i < mAllAssignee.size(); i++) {
String login = mAllAssignee.get(i).getLogin();
assigneeNames[i] = login;
selection[i] = assigneeLogins.contains(login);
}
DialogInterface.OnMultiChoiceClickListener selectCb = new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which, boolean isChecked) {
selection[which] = isChecked;
}
};
DialogInterface.OnClickListener okCb = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
List<User> newAssigneeList = new ArrayList<>();
for (int i = 0; i < selection.length; i++) {
if (selection[i]) {
newAssigneeList.add(mAllAssignee.get(i));
}
}
mEditIssue.setAssignees(newAssigneeList);
updateLabels();
dialog.dismiss();
}
};
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(R.string.issue_assignee_hint)
.setMultiChoiceItems(assigneeNames, selection, selectCb)
.setPositiveButton(R.string.ok, okCb)
.setNegativeButton(R.string.cancel, null)
.show();
}
}
private void showLabelDialog() {
if (mAllLabels == null) {
getSupportLoaderManager().initLoader(0, null, mLabelCallback);
} else {
LayoutInflater inflater = getLayoutInflater();
final List<Label> selectedLabels = mEditIssue.getLabels() != null
? new ArrayList<>(mEditIssue.getLabels()) : new ArrayList<Label>();
View labelContainerView = inflater.inflate(R.layout.generic_linear_container, null);
ViewGroup container = (ViewGroup) labelContainerView.findViewById(R.id.container);
View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
Label label = (Label) view.getTag();
if (selectedLabels.contains(label)) {
selectedLabels.remove(label);
setLabelSelection((TextView) view, false);
} else {
selectedLabels.add(label);
setLabelSelection((TextView) view, true);
}
}
};
for (final Label label : mAllLabels) {
final View rowView = inflater.inflate(R.layout.row_issue_create_label, container, false);
View viewColor = rowView.findViewById(R.id.view_color);
viewColor.setBackgroundColor(ApiHelpers.colorForLabel(label));
final TextView tvLabel = (TextView) rowView.findViewById(R.id.tv_title);
tvLabel.setText(label.getName());
tvLabel.setOnClickListener(clickListener);
tvLabel.setTag(label);
setLabelSelection(tvLabel, selectedLabels.contains(label));
container.addView(rowView);
}
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(R.string.issue_labels)
.setView(labelContainerView)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mEditIssue.setLabels(selectedLabels);
updateLabels();
}
})
.setNeutralButton(R.string.issue_manage_labels,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = IssueLabelListActivity.makeIntent(
IssueEditActivity.this, mRepoOwner, mRepoName,
mEditIssue.getPullRequest() != null);
startActivityForResult(intent, REQUEST_MANAGE_LABELS);
}
})
.show();
}
}
private void setLabelSelection(TextView view, boolean selected) {
Label label = (Label) view.getTag();
if (selected) {
int color = ApiHelpers.colorForLabel(label);
view.setTypeface(view.getTypeface(), Typeface.BOLD);
view.setBackgroundColor(color);
view.setTextColor(UiUtils.textColorForBackground(this, color));
} else {
view.setTypeface(view.getTypeface(), 0);
view.setBackgroundColor(0);
view.setTextColor(ContextCompat.getColor(this, Gh4Application.THEME != R.style.LightTheme
? R.color.label_fg_light : R.color.label_fg_dark));
}
}
private class SaveIssueTask extends ProgressDialogTask<Issue> {
private final Issue mIssue;
public SaveIssueTask(Issue issue) {
super(IssueEditActivity.this, R.string.saving_msg);
Milestone milestone = issue.getMilestone();
mIssue = new Issue()
.setNumber(issue.getNumber())
.setTitle(issue.getTitle())
.setBody(issue.getBody())
.setMilestone(milestone != null ? milestone : new Milestone())
.setAssignees(issue.getAssignees())
.setLabels(issue.getLabels());
}
@Override
protected ProgressDialogTask<Issue> clone() {
return new SaveIssueTask(mIssue);
}
@Override
protected Issue run() throws IOException {
IssueService issueService = (IssueService)
Gh4Application.get().getService(Gh4Application.ISSUE_SERVICE);
if (isInEditMode()) {
return issueService.editIssue(mRepoOwner, mRepoName, mIssue);
} else {
return issueService.createIssue(mRepoOwner, mRepoName, mIssue);
}
}
@Override
protected void onSuccess(Issue result) {
Intent data = new Intent();
Bundle extras = new Bundle();
extras.putSerializable("issue", result);
data.putExtras(extras);
setResult(RESULT_OK, data);
finish();
}
@Override
protected String getErrorMessage() {
if (isInEditMode()) {
return getContext().getString(R.string.issue_error_edit, mIssue.getNumber());
} else {
return getContext().getString(R.string.issue_error_create);
}
}
}
private void updateLabels() {
if (mEditIssue.getMilestone() != null) {
mTvSelectedMilestone.setText(mEditIssue.getMilestone().getTitle());
} else if (!mIsCollaborator && !isInEditMode()) {
mTvSelectedMilestone.setText(R.string.issue_milestone_collab_only);
} else {
mTvSelectedMilestone.setText(R.string.issue_clear_milestone);
}
List<User> assignees = mEditIssue.getAssignees();
if (assignees != null && !assignees.isEmpty()) {
LayoutInflater inflater = getLayoutInflater();
mSelectedAssigneeContainer.removeAllViews();
for (User assignee : assignees) {
View row = inflater.inflate(R.layout.row_assignee, mSelectedAssigneeContainer, false);
TextView tvAssignee = (TextView) row.findViewById(R.id.tv_assignee);
tvAssignee.setText(ApiHelpers.getUserLogin(this, assignee));
ImageView ivAssignee = (ImageView) row.findViewById(R.id.iv_assignee);
AvatarHandler.assignAvatar(ivAssignee, assignee);
mSelectedAssigneeContainer.addView(row);
}
mTvSelectedAssignee.setVisibility(View.GONE);
mSelectedAssigneeContainer.setVisibility(View.VISIBLE);
} else if (!mIsCollaborator && !isInEditMode()) {
mTvSelectedAssignee.setText(R.string.issue_assignee_collab_only);
mTvSelectedAssignee.setVisibility(View.VISIBLE);
mSelectedAssigneeContainer.setVisibility(View.GONE);
} else {
mTvSelectedAssignee.setText(R.string.issue_clear_assignee);
mTvSelectedAssignee.setVisibility(View.VISIBLE);
mSelectedAssigneeContainer.setVisibility(View.GONE);
}
List<Label> labels = mEditIssue.getLabels();
if (!mIsCollaborator && !isInEditMode()) {
mTvLabels.setText(R.string.issue_labels_collab_only);
} else if (labels == null || labels.isEmpty()) {
mTvLabels.setText(R.string.issue_no_labels);
} else {
mTvLabels.setText(UiUtils.formatLabelList(this, labels));
}
}
private void updateLabelStates() {
mMilestoneContainer.setEnabled(mIsCollaborator);
findViewById(R.id.tv_milestone_label).setEnabled(mIsCollaborator);
mTvSelectedMilestone.setEnabled(mIsCollaborator);
mAssigneeContainer.setEnabled(mIsCollaborator);
findViewById(R.id.tv_assignee_label).setEnabled(mIsCollaborator);
mTvSelectedAssignee.setEnabled(mIsCollaborator);
mLabelContainer.setEnabled(mIsCollaborator);
findViewById(R.id.tv_labels_label).setEnabled(mIsCollaborator);
mTvLabels.setEnabled(mIsCollaborator);
}
}