Java tutorial
/* * 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 java.io.IOException; import java.util.List; import java.util.Locale; import org.eclipse.egit.github.core.Comment; import org.eclipse.egit.github.core.Issue; import org.eclipse.egit.github.core.Label; import org.eclipse.egit.github.core.RepositoryId; import org.eclipse.egit.github.core.service.IssueService; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.support.design.widget.CoordinatorLayout; import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v7.app.ActionBar; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.SpannableStringBuilder; 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.ImageView; import android.widget.TextView; import com.gh4a.BaseActivity; import com.gh4a.Constants; import com.gh4a.Gh4Application; import com.gh4a.ProgressDialogTask; import com.gh4a.R; import com.gh4a.adapter.IssueEventAdapter; import com.gh4a.fragment.CommentBoxFragment; import com.gh4a.loader.IsCollaboratorLoader; import com.gh4a.loader.IssueCommentListLoader; import com.gh4a.loader.IssueEventHolder; import com.gh4a.loader.IssueLoader; import com.gh4a.loader.LoaderCallbacks; import com.gh4a.loader.LoaderResult; import com.gh4a.utils.ApiHelpers; import com.gh4a.utils.AvatarHandler; import com.gh4a.utils.IntentUtils; import com.gh4a.utils.StringUtils; import com.gh4a.utils.UiUtils; import com.gh4a.widget.DividerItemDecoration; import com.gh4a.widget.IssueLabelSpan; import com.gh4a.widget.IssueStateTrackingFloatingActionButton; import com.gh4a.widget.SwipeRefreshLayout; import com.github.mobile.util.HtmlUtils; import com.github.mobile.util.HttpImageGetter; public class IssueActivity extends BaseActivity implements View.OnClickListener, IssueEventAdapter.OnEditComment, SwipeRefreshLayout.ChildScrollDelegate, CommentBoxFragment.Callback { private static final int REQUEST_EDIT = 1000; private static final int REQUEST_EDIT_ISSUE = 1001; private Issue mIssue; private String mRepoOwner; private String mRepoName; private int mIssueNumber; private ViewGroup mHeader; private View mListHeaderView; private boolean mEventsLoaded; private IssueEventAdapter mEventAdapter; private RecyclerView mRecyclerView; private boolean mIsCollaborator; private IssueStateTrackingFloatingActionButton mEditFab; private CommentBoxFragment mCommentFragment; private HttpImageGetter mImageGetter; private Handler mHandler = new Handler(); private 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; fillDataIfDone(); supportInvalidateOptionsMenu(); } }; private LoaderCallbacks<List<IssueEventHolder>> mEventCallback = new LoaderCallbacks<List<IssueEventHolder>>( this) { @Override protected Loader<LoaderResult<List<IssueEventHolder>>> onCreateLoader() { return new IssueCommentListLoader(IssueActivity.this, mRepoOwner, mRepoName, mIssueNumber); } @Override protected void onResultReady(List<IssueEventHolder> result) { mEventAdapter.clear(); if (result != null) { mEventAdapter.addAll(result); } mEventAdapter.notifyDataSetChanged(); mEventsLoaded = true; fillDataIfDone(); } }; private 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; updateFabVisibility(); updateCommentLockState(); supportInvalidateOptionsMenu(); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.issue); setContentShown(false); ActionBar actionBar = getSupportActionBar(); actionBar.setTitle(getString(R.string.issue) + " #" + mIssueNumber); actionBar.setSubtitle(mRepoOwner + "/" + mRepoName); actionBar.setDisplayHomeAsUpEnabled(true); mRecyclerView = (RecyclerView) findViewById(R.id.list); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.addItemDecoration(new DividerItemDecoration(this)); mImageGetter = new HttpImageGetter(this); LayoutInflater inflater = getLayoutInflater(); mHeader = (ViewGroup) inflater.inflate(R.layout.issue_header, null); mHeader.setClickable(false); mHeader.setVisibility(View.GONE); addHeaderView(mHeader, false); mListHeaderView = inflater.inflate(R.layout.issue_comment_list_header, mRecyclerView, false); mEventAdapter = new IssueEventAdapter(this, mRepoOwner, mRepoName, mIssueNumber, this); mEventAdapter.setHeaderView(mListHeaderView); mRecyclerView.setAdapter(mEventAdapter); setChildScrollDelegate(this); if (!Gh4Application.get().isAuthorized()) { findViewById(R.id.comment_box).setVisibility(View.GONE); } FragmentManager fm = getSupportFragmentManager(); mCommentFragment = (CommentBoxFragment) fm.findFragmentById(R.id.comment_box); setToolbarScrollable(true); getSupportLoaderManager().initLoader(0, null, mIssueCallback); getSupportLoaderManager().initLoader(1, null, mCollaboratorCallback); getSupportLoaderManager().initLoader(2, null, mEventCallback); } @Override protected void onInitExtras(Bundle extras) { super.onInitExtras(extras); mRepoOwner = extras.getString(Constants.Repository.OWNER); mRepoName = extras.getString(Constants.Repository.NAME); mIssueNumber = extras.getInt(Constants.Issue.NUMBER); } @Override protected void onDestroy() { super.onDestroy(); mImageGetter.destroy(); if (mEventAdapter != null) { mEventAdapter.destroy(); } } @Override protected void onResume() { super.onResume(); mEventAdapter.resume(); mImageGetter.resume(); } @Override protected void onPause() { super.onPause(); mEventAdapter.pause(); mImageGetter.pause(); } @Override public boolean canChildScrollUp() { if (mCommentFragment != null && mCommentFragment.canChildScrollUp()) { return true; } return UiUtils.canViewScrollUp(mRecyclerView); } private void fillDataIfDone() { if (mIssue == null || !mEventsLoaded) { return; } fillData(); setContentShown(true); } private void fillData() { // set details inside listview header ImageView ivGravatar = (ImageView) mListHeaderView.findViewById(R.id.iv_gravatar); AvatarHandler.assignAvatar(ivGravatar, mIssue.getUser()); ivGravatar.setOnClickListener(this); TextView tvState = (TextView) mHeader.findViewById(R.id.tv_state); boolean closed = Constants.Issue.STATE_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); UiUtils.trySetListOverscrollColor(mRecyclerView, UiUtils.resolveColor(this, stateColorAttributeId)); TextView tvExtra = (TextView) mListHeaderView.findViewById(R.id.tv_extra); tvExtra.setText(ApiHelpers.getUserLogin(this, mIssue.getUser())); TextView tvTimestamp = (TextView) mListHeaderView.findViewById(R.id.tv_timestamp); tvTimestamp.setText(StringUtils.formatRelativeTime(this, mIssue.getCreatedAt(), true)); TextView tvTitle = (TextView) mHeader.findViewById(R.id.tv_title); tvTitle.setText(mIssue.getTitle()); String body = mIssue.getBodyHtml(); TextView descriptionView = (TextView) mListHeaderView.findViewById(R.id.tv_desc); if (!StringUtils.isBlank(body)) { body = HtmlUtils.format(body).toString(); mImageGetter.bind(descriptionView, body, mIssue.getNumber()); } descriptionView.setMovementMethod(UiUtils.CHECKING_LINK_METHOD); View milestoneGroup = mListHeaderView.findViewById(R.id.milestone_container); if (mIssue.getMilestone() != null) { TextView tvMilestone = (TextView) mListHeaderView.findViewById(R.id.tv_milestone); tvMilestone.setText(mIssue.getMilestone().getTitle()); milestoneGroup.setVisibility(View.VISIBLE); } else { milestoneGroup.setVisibility(View.GONE); } View assigneeGroup = mListHeaderView.findViewById(R.id.assignee_container); if (mIssue.getAssignee() != null) { TextView tvAssignee = (TextView) mListHeaderView.findViewById(R.id.tv_assignee); tvAssignee.setText(mIssue.getAssignee().getLogin()); ImageView ivAssignee = (ImageView) mListHeaderView.findViewById(R.id.iv_assignee); AvatarHandler.assignAvatar(ivAssignee, mIssue.getAssignee()); ivAssignee.setOnClickListener(this); assigneeGroup.setVisibility(View.VISIBLE); } else { assigneeGroup.setVisibility(View.GONE); } List<Label> labels = mIssue.getLabels(); View labelGroup = mListHeaderView.findViewById(R.id.label_container); if (labels != null && !labels.isEmpty()) { TextView labelView = (TextView) mListHeaderView.findViewById(R.id.labels); SpannableStringBuilder builder = new SpannableStringBuilder(); for (Label label : labels) { int pos = builder.length(); IssueLabelSpan span = new IssueLabelSpan(this, label, true); builder.append(label.getName()); builder.setSpan(span, pos, pos + label.getName().length(), 0); } labelView.setText(builder); labelGroup.setVisibility(View.VISIBLE); } else { labelGroup.setVisibility(View.GONE); } TextView tvPull = (TextView) mListHeaderView.findViewById(R.id.tv_pull); if (mIssue.getPullRequest() != null && mIssue.getPullRequest().getDiffUrl() != null) { tvPull.setVisibility(View.VISIBLE); tvPull.setOnClickListener(this); } else { tvPull.setVisibility(View.GONE); } mHeader.setVisibility(View.VISIBLE); updateFabVisibility(); updateCommentLockState(); } @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 canOpenOrClose = mIssue != null && authorized && (isCreator || mIsCollaborator); if (!canOpenOrClose) { menu.removeItem(R.id.issue_close); menu.removeItem(R.id.issue_reopen); } else if (Constants.Issue.STATE_CLOSED.equals(mIssue.getState())) { menu.removeItem(R.id.issue_close); } else { menu.removeItem(R.id.issue_reopen); } if (mIssue == null) { menu.removeItem(R.id.share); } return super.onCreateOptionsMenu(menu); } @Override protected Intent navigateUp() { return IntentUtils.getIssueListActivityIntent(this, mRepoOwner, mRepoName, Constants.Issue.STATE_OPEN); } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); switch (itemId) { case R.id.issue_close: case R.id.issue_reopen: if (checkForAuthOrExit()) { new IssueOpenCloseTask(itemId == R.id.issue_reopen).schedule(); } 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; } return super.onOptionsItemSelected(item); } @Override public void onRefresh() { mIssue = null; mEventsLoaded = false; mIsCollaborator = false; setContentShown(false); transitionHeaderToColor(R.attr.colorPrimary, R.attr.colorPrimaryDark); mHeader.setVisibility(View.GONE); // 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(); updateCommentLockState(); } }); LoaderManager lm = getSupportLoaderManager(); for (int i = 0; i < 3; i++) { lm.getLoader(i).onContentChanged(); } super.onRefresh(); } private void updateCommentLockState() { boolean locked = mIssue != null && mIssue.isLocked() && !mIsCollaborator; mCommentFragment.setLocked(locked); } private void updateFabVisibility() { boolean isIssueOwner = mIssue != null && ApiHelpers.loginEquals(mIssue.getUser(), Gh4Application.get().getAuthLogin()); boolean shouldHaveFab = (isIssueOwner || mIsCollaborator) && mIssue != null && mEventsLoaded; 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) { Intent intent = null; switch (v.getId()) { case R.id.edit_fab: if (checkForAuthOrExit()) { Intent editIntent = new Intent(this, IssueEditActivity.class); editIntent.putExtra(Constants.Repository.OWNER, mRepoOwner); editIntent.putExtra(Constants.Repository.NAME, mRepoName); editIntent.putExtra(IssueEditActivity.EXTRA_ISSUE, mIssue); startActivityForResult(editIntent, REQUEST_EDIT_ISSUE); } break; case R.id.iv_gravatar: intent = IntentUtils.getUserActivityIntent(this, mIssue.getUser()); break; case R.id.tv_assignee: case R.id.iv_assignee: intent = IntentUtils.getUserActivityIntent(this, mIssue.getAssignee()); break; case R.id.tv_pull: intent = IntentUtils.getPullRequestActivityIntent(this, mRepoOwner, mRepoName, mIssueNumber); break; } if (intent != null) { startActivity(intent); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_EDIT) { if (resultCode == Activity.RESULT_OK) { getSupportLoaderManager().getLoader(2).onContentChanged(); setResult(RESULT_OK); } } else if (requestCode == REQUEST_EDIT_ISSUE) { if (resultCode == Activity.RESULT_OK) { getSupportLoaderManager().getLoader(0).onContentChanged(); setResult(RESULT_OK); } } else { super.onActivityResult(requestCode, resultCode, data); } } @Override public void editComment(Comment comment) { Intent intent = new Intent(this, EditIssueCommentActivity.class); intent.putExtra(Constants.Repository.OWNER, mRepoOwner); intent.putExtra(Constants.Repository.NAME, mRepoName); intent.putExtra(Constants.Comment.ID, comment.getId()); intent.putExtra(Constants.Comment.BODY, comment.getBody()); startActivityForResult(intent, REQUEST_EDIT); } @Override public int getCommentEditorHintResId() { return R.string.issue_comment_hint; } @Override public void onSendCommentInBackground(String comment) throws IOException { IssueService issueService = (IssueService) Gh4Application.get().getService(Gh4Application.ISSUE_SERVICE); issueService.createComment(mRepoOwner, mRepoName, mIssueNumber, comment); } @Override public void onCommentSent() { //reload comments Loader loader = getSupportLoaderManager().getLoader(2); if (loader != null) { loader.onContentChanged(); } setResult(RESULT_OK); } private class IssueOpenCloseTask extends ProgressDialogTask<Issue> { private boolean mOpen; public IssueOpenCloseTask(boolean open) { super(IssueActivity.this, 0, 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 ? Constants.Issue.STATE_OPEN : Constants.Issue.STATE_CLOSED); return issueService.editIssue(repoId, issue); } @Override protected void onSuccess(Issue result) { mIssue = result; // reload issue state fillDataIfDone(); // reload events, the action will have triggered an additional one getSupportLoaderManager().getLoader(2).onContentChanged(); setResult(RESULT_OK); supportInvalidateOptionsMenu(); } @Override protected String getErrorMessage() { return getContext().getString(R.string.issue_error_close, mIssueNumber); } } }