/*
* 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.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.StringRes;
import android.support.design.widget.CoordinatorLayout;
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.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.gh4a.BaseActivity;
import com.gh4a.Gh4Application;
import com.gh4a.ProgressDialogTask;
import com.gh4a.R;
import com.gh4a.fragment.IssueFragment;
import com.gh4a.loader.IsCollaboratorLoader;
import com.gh4a.loader.IssueLoader;
import com.gh4a.loader.LoaderCallbacks;
import com.gh4a.loader.LoaderResult;
import com.gh4a.utils.ApiHelpers;
import com.gh4a.utils.IntentUtils;
import com.gh4a.utils.UiUtils;
import com.gh4a.widget.IssueStateTrackingFloatingActionButton;
import org.eclipse.egit.github.core.Issue;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.service.IssueService;
import java.io.IOException;
import java.util.Locale;
public class IssueActivity extends BaseActivity implements View.OnClickListener {
public static Intent makeIntent(Context context, String login, String repoName, int number) {
return makeIntent(context, login, repoName, number, null);
}
public static Intent makeIntent(Context context, String login, String repoName,
int number, IntentUtils.InitialCommentMarker initialComment) {
return new Intent(context, IssueActivity.class)
.putExtra("owner", login)
.putExtra("repo", repoName)
.putExtra("number", number)
.putExtra("initial_comment", initialComment);
}
private static final int REQUEST_EDIT_ISSUE = 1000;
private Issue mIssue;
private String mRepoOwner;
private String mRepoName;
private int mIssueNumber;
private IntentUtils.InitialCommentMarker mInitialComment;
private ViewGroup mHeader;
private Boolean mIsCollaborator;
private IssueStateTrackingFloatingActionButton mEditFab;
private final Handler mHandler = new Handler();
private IssueFragment mFragment;
private final LoaderCallbacks<Issue> mIssueCallback = new LoaderCallbacks<Issue>(this) {
@Override
protected Loader<LoaderResult<Issue>> onCreateLoader() {
return new IssueLoader(IssueActivity.this, mRepoOwner, mRepoName, mIssueNumber);
}
@Override
protected void onResultReady(Issue result) {
mIssue = result;
showUiIfDone();
supportInvalidateOptionsMenu();
}
};
private final LoaderCallbacks<Boolean> mCollaboratorCallback = new LoaderCallbacks<Boolean>(this) {
@Override
protected Loader<LoaderResult<Boolean>> onCreateLoader() {
return new IsCollaboratorLoader(IssueActivity.this, mRepoOwner, mRepoName);
}
@Override
protected void onResultReady(Boolean result) {
mIsCollaborator = result;
showUiIfDone();
supportInvalidateOptionsMenu();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frame_layout);
setContentShown(false);
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(getString(R.string.issue) + " #" + mIssueNumber);
actionBar.setSubtitle(mRepoOwner + "/" + mRepoName);
actionBar.setDisplayHomeAsUpEnabled(true);
LayoutInflater inflater = LayoutInflater.from(UiUtils.makeHeaderThemedContext(this));
mHeader = (ViewGroup) inflater.inflate(R.layout.issue_header, null);
mHeader.setClickable(false);
mHeader.setVisibility(View.GONE);
addHeaderView(mHeader, false);
setFragment((IssueFragment) getSupportFragmentManager().findFragmentById(R.id.details));
setToolbarScrollable(true);
getSupportLoaderManager().initLoader(0, null, mIssueCallback);
getSupportLoaderManager().initLoader(1, null, mCollaboratorCallback);
}
@Override
protected void onInitExtras(Bundle extras) {
super.onInitExtras(extras);
mRepoOwner = extras.getString("owner");
mRepoName = extras.getString("repo");
mIssueNumber = extras.getInt("number");
mInitialComment = extras.getParcelable("initial_comment");
extras.remove("initial_comment");
}
private void showUiIfDone() {
if (mIssue == null || mIsCollaborator == null) {
return;
}
setFragment(IssueFragment.newInstance(mRepoOwner, mRepoName,
mIssue, mIsCollaborator, mInitialComment));
getSupportFragmentManager().beginTransaction()
.replace(R.id.details, mFragment)
.commitAllowingStateLoss();
mInitialComment = null;
updateHeader();
updateFabVisibility();
setContentShown(true);
}
private void setFragment(IssueFragment fragment) {
mFragment = fragment;
setChildScrollDelegate(fragment);
}
private void updateHeader() {
TextView tvState = (TextView) mHeader.findViewById(R.id.tv_state);
boolean closed = ApiHelpers.IssueState.CLOSED.equals(mIssue.getState());
int stateTextResId = closed ? R.string.closed : R.string.open;
int stateColorAttributeId = closed ? R.attr.colorIssueClosed : R.attr.colorIssueOpen;
tvState.setText(getString(stateTextResId).toUpperCase(Locale.getDefault()));
transitionHeaderToColor(stateColorAttributeId,
closed ? R.attr.colorIssueClosedDark : R.attr.colorIssueOpenDark);
TextView tvTitle = (TextView) mHeader.findViewById(R.id.tv_title);
tvTitle.setText(mIssue.getTitle());
mHeader.setVisibility(View.VISIBLE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.issue_menu, menu);
boolean authorized = Gh4Application.get().isAuthorized();
boolean isCreator = mIssue != null && authorized &&
ApiHelpers.loginEquals(mIssue.getUser(), Gh4Application.get().getAuthLogin());
boolean isClosed = mIssue != null && ApiHelpers.IssueState.CLOSED.equals(mIssue.getState());
boolean isCollaborator = mIsCollaborator != null && mIsCollaborator;
boolean closerIsCreator = mIssue != null
&& ApiHelpers.userEquals(mIssue.getUser(), mIssue.getClosedBy());
boolean canClose = mIssue != null && authorized && (isCreator || isCollaborator);
boolean canOpen = canClose && (isCollaborator || closerIsCreator);
if (!canClose || isClosed) {
menu.removeItem(R.id.issue_close);
}
if (!canOpen || !isClosed) {
menu.removeItem(R.id.issue_reopen);
}
if (mIssue == null) {
menu.removeItem(R.id.browser);
menu.removeItem(R.id.share);
}
return super.onCreateOptionsMenu(menu);
}
@Override
protected Intent navigateUp() {
return IssueListActivity.makeIntent(this, mRepoOwner, mRepoName);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.issue_close:
case R.id.issue_reopen:
if (checkForAuthOrExit()) {
showOpenCloseConfirmDialog(itemId == R.id.issue_reopen);
}
return true;
case R.id.share:
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_issue_subject,
mIssueNumber, mIssue.getTitle(), mRepoOwner + "/" + mRepoName));
shareIntent.putExtra(Intent.EXTRA_TEXT, mIssue.getHtmlUrl());
shareIntent = Intent.createChooser(shareIntent, getString(R.string.share_title));
startActivity(shareIntent);
return true;
case R.id.browser:
IntentUtils.launchBrowser(this, Uri.parse(mIssue.getHtmlUrl()));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRefresh() {
mIssue = null;
mIsCollaborator = null;
setContentShown(false);
transitionHeaderToColor(R.attr.colorPrimary, R.attr.colorPrimaryDark);
mHeader.setVisibility(View.GONE);
if (mFragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(mFragment)
.commit();
setFragment(null);
}
// onRefresh() can be triggered in the draw loop, and CoordinatorLayout doesn't
// like its child list being changed while drawing
mHandler.post(new Runnable() {
@Override
public void run() {
updateFabVisibility();
}
});
forceLoaderReload(0, 1);
super.onRefresh();
}
private void showOpenCloseConfirmDialog(final boolean reopen) {
@StringRes int messageResId = reopen
? R.string.reopen_issue_confirm : R.string.close_issue_confirm;
@StringRes int buttonResId = reopen
? R.string.pull_request_reopen : R.string.pull_request_close;
new AlertDialog.Builder(this)
.setMessage(messageResId)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setCancelable(false)
.setPositiveButton(buttonResId, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new IssueOpenCloseTask(reopen).schedule();
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
private void updateFabVisibility() {
boolean isIssueOwner = mIssue != null
&& ApiHelpers.loginEquals(mIssue.getUser(), Gh4Application.get().getAuthLogin());
boolean isCollaborator = mIsCollaborator != null && mIsCollaborator;
boolean shouldHaveFab = (isIssueOwner || isCollaborator) && mIssue != null;
CoordinatorLayout rootLayout = getRootLayout();
if (shouldHaveFab && mEditFab == null) {
mEditFab = (IssueStateTrackingFloatingActionButton)
getLayoutInflater().inflate(R.layout.issue_edit_fab, rootLayout, false);
mEditFab.setOnClickListener(this);
rootLayout.addView(mEditFab);
} else if (!shouldHaveFab && mEditFab != null) {
rootLayout.removeView(mEditFab);
mEditFab = null;
}
if (mEditFab != null) {
mEditFab.setState(mIssue.getState());
}
}
private boolean checkForAuthOrExit() {
if (Gh4Application.get().isAuthorized()) {
return true;
}
Intent intent = new Intent(this, Github4AndroidActivity.class);
startActivity(intent);
finish();
return false;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.edit_fab && checkForAuthOrExit()) {
Intent editIntent = IssueEditActivity.makeEditIntent(this,
mRepoOwner, mRepoName, mIssue);
startActivityForResult(editIntent, REQUEST_EDIT_ISSUE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_EDIT_ISSUE) {
if (resultCode == Activity.RESULT_OK) {
forceLoaderReload(0);
setResult(RESULT_OK);
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private class IssueOpenCloseTask extends ProgressDialogTask<Issue> {
private final boolean mOpen;
public IssueOpenCloseTask(boolean open) {
super(IssueActivity.this, open ? R.string.opening_msg : R.string.closing_msg);
mOpen = open;
}
@Override
protected ProgressDialogTask<Issue> clone() {
return new IssueOpenCloseTask(mOpen);
}
@Override
protected Issue run() throws IOException {
IssueService issueService = (IssueService)
Gh4Application.get().getService(Gh4Application.ISSUE_SERVICE);
RepositoryId repoId = new RepositoryId(mRepoOwner, mRepoName);
Issue issue = issueService.getIssue(repoId, mIssueNumber);
issue.setState(mOpen ? ApiHelpers.IssueState.OPEN : ApiHelpers.IssueState.CLOSED);
return issueService.editIssue(repoId, issue);
}
@Override
protected void onSuccess(Issue result) {
mIssue = result;
updateHeader();
if (mEditFab != null) {
mEditFab.setState(mIssue.getState());
}
if (mFragment != null) {
mFragment.updateState(mIssue);
}
setResult(RESULT_OK);
supportInvalidateOptionsMenu();
}
@Override
protected String getErrorMessage() {
@StringRes int messageResId = mOpen
? R.string.issue_error_reopen : R.string.issue_error_close;
return getContext().getString(messageResId, mIssueNumber);
}
}
}