package com.alorma.github.ui.activity; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.EditText; import android.widget.ImageView; import butterknife.BindView; import butterknife.ButterKnife; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.StackingBehavior; import com.alorma.github.GitskariosSettings; import com.alorma.github.R; import com.alorma.github.StoreCredentials; import com.alorma.github.injector.component.ApiComponent; import com.alorma.github.injector.component.ApplicationComponent; import com.alorma.github.injector.component.DaggerApiComponent; import com.alorma.github.injector.module.ApiModule; import com.alorma.github.injector.module.issues.IssueDetailModule; import com.alorma.github.presenter.issue.IssueCommentPresenter; import com.alorma.github.sdk.bean.dto.request.CreateMilestoneRequestDTO; import com.alorma.github.sdk.bean.dto.request.EditIssueBodyRequestDTO; import com.alorma.github.sdk.bean.dto.request.EditIssueLabelsRequestDTO; import com.alorma.github.sdk.bean.dto.request.EditIssueMilestoneRequestDTO; 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.response.Issue; import com.alorma.github.sdk.bean.dto.response.IssueState; import com.alorma.github.sdk.bean.dto.response.Milestone; import com.alorma.github.sdk.bean.dto.response.MilestoneState; import com.alorma.github.sdk.bean.info.IssueInfo; import com.alorma.github.sdk.bean.info.RepoInfo; import com.alorma.github.sdk.bean.issue.IssueStory; import com.alorma.github.sdk.bean.issue.IssueStoryComment; import com.alorma.github.sdk.services.issues.CreateMilestoneClient; import com.alorma.github.sdk.services.issues.EditIssueClient; import com.alorma.github.sdk.services.issues.GetMilestonesClient; import com.alorma.github.sdk.services.issues.GithubIssueLabelsClient; import com.alorma.github.sdk.services.issues.story.IssueStoryLoader; import com.alorma.github.sdk.services.repo.GetRepoClient; import com.alorma.github.ui.ErrorHandler; import com.alorma.github.ui.actions.ActionCallback; import com.alorma.github.ui.actions.AddIssueCommentAction; import com.alorma.github.ui.actions.ChangeAssigneeAction; import com.alorma.github.ui.actions.CloseAction; import com.alorma.github.ui.actions.ReopenAction; import com.alorma.github.ui.actions.ShareAction; import com.alorma.github.ui.activity.base.BackActivity; import com.alorma.github.ui.adapter.issues.IssueDetailAdapter; 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.utils.AttributesUtils; import com.alorma.github.utils.ShortcutUtils; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ArgbEvaluator; import com.nineoldandroids.animation.ValueAnimator; import core.GithubComment; import core.issue.EditIssueCommentBodyRequest; import core.issues.Label; import core.repositories.Repo; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import rx.Observer; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; public class IssueDetailActivity extends BackActivity implements View.OnClickListener, IssueDetailRequestListener, SwipeRefreshLayout.OnRefreshListener, IssueCommentRequestListener, com.alorma.github.presenter.View<GithubComment> { @Inject IssueCommentPresenter issueCommentPresenter; 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 static final int EDIT_COMMENT_REQUEST = 1121; public Repo repository; private boolean shouldRefreshOnBack; private IssueInfo issueInfo; @BindView(R.id.swipe) SwipeRefreshLayout swipe; @BindView(R.id.recycler) RecyclerView recyclerView; @BindView(R.id.addCommentEditText) EditText addCommentEditText; @BindView(R.id.addCommentButton) ImageView addCommentButton; @BindView(R.id.addCommentButtonExpand) ImageView addCommentButtonExpand; private IssueStory issueStory; private int primary; private int primaryDark; private ApiComponent apiComponent; public static Intent createLauncherIntent(Context context, IssueInfo issueInfo) { Bundle bundle = new Bundle(); bundle.putString(ISSUE_INFO_REPO_NAME, issueInfo.repoInfo.name); bundle.putString(ISSUE_INFO_REPO_OWNER, issueInfo.repoInfo.owner); bundle.putInt(ISSUE_INFO_NUMBER, issueInfo.num); Intent intent = new Intent(context, IssueDetailActivity.class); intent.putExtras(bundle); return intent; } public static Intent createShortcutLauncherIntent(Context context, IssueInfo issueInfo) { Bundle bundle = new Bundle(); bundle.putString(ISSUE_INFO_REPO_NAME, issueInfo.repoInfo.name); bundle.putString(ISSUE_INFO_REPO_OWNER, issueInfo.repoInfo.owner); bundle.putInt(ISSUE_INFO_NUMBER, issueInfo.num); Intent intent = new Intent(context, IssueDetailActivity.class); intent.putExtras(bundle); return intent; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.issue_detail_activity); issueCommentPresenter.attachView(this); if (getIntent().getExtras() != null) { String name = getIntent().getExtras().getString(ISSUE_INFO_REPO_NAME); String owner = getIntent().getExtras().getString(ISSUE_INFO_REPO_OWNER); RepoInfo repoInfo = new RepoInfo(); repoInfo.name = name; repoInfo.owner = owner; int num = getIntent().getExtras().getInt(ISSUE_INFO_NUMBER); issueInfo = new IssueInfo(); issueInfo.repoInfo = repoInfo; issueInfo.num = num; primary = AttributesUtils.getPrimaryColor(this); primaryDark = ContextCompat.getColor(this, R.color.primary_dark_alpha); initViews(); getContent(); } } @Override protected void onDestroy() { super.onDestroy(); issueCommentPresenter.detachView(); } @Override protected void injectComponents(ApplicationComponent applicationComponent) { super.injectComponents(applicationComponent); apiComponent = DaggerApiComponent.builder().applicationComponent(applicationComponent).apiModule(new ApiModule()).build(); apiComponent.plus(new IssueDetailModule()).inject(this); } private void checkEditTitle() { if (issueInfo != null && issueStory != null && issueStory.item != null) { StoreCredentials credentials = new StoreCredentials(this); GitskariosSettings settings = new GitskariosSettings(this); 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 (issueStory.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(this).title(R.string.dialog_edit_issue).content(content).positiveText(R.string.ok).show(); } private void initViews() { ButterKnife.bind(this); recyclerView.setLayoutManager(new LinearLayoutManager(this)); swipe.setColorSchemeResources(R.color.accent_dark); } @Override protected void getContent() { super.getContent(); hideProgressDialog(); GetRepoClient repoClient = new GetRepoClient(issueInfo.repoInfo); repoClient.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(repo -> { issueInfo.repoInfo.permissions = repo.permissions; repository = repo; loadIssue(); }, Throwable::printStackTrace); } private void loadIssue() { IssueStoryLoader issueStoryLoader = new IssueStoryLoader(issueInfo); issueStoryLoader.observable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::onResponseOk, Throwable::printStackTrace); } public void onResponseOk(IssueStory issueStory) { this.issueStory = issueStory; this.issueStory.item.repository = repository; swipe.setRefreshing(false); swipe.setOnRefreshListener(this); checkEditTitle(); applyIssue(); addCommentEditText.setEnabled(!issueStory.item.locked); addCommentButton.setEnabled(!issueStory.item.locked); if (!issueStory.item.locked) { addCommentEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { if (addCommentEditText.getText().toString().isEmpty()) { addCommentButton.setVisibility(View.GONE); addCommentButtonExpand.setVisibility(View.GONE); addCommentButton.setOnClickListener(null); addCommentButtonExpand.setOnClickListener(null); } else { addCommentButton.setVisibility(View.VISIBLE); addCommentButtonExpand.setVisibility(View.VISIBLE); addCommentButton.setOnClickListener(IssueDetailActivity.this); addCommentButtonExpand.setOnClickListener(IssueDetailActivity.this); } } @Override public void afterTextChanged(Editable editable) { } }); } } private void applyIssue() { changeColor(issueStory.item); String status = getString(R.string.issue_status_open); if (IssueState.closed == issueStory.item.state) { status = getString(R.string.issue_status_close); } setTitle("#" + issueStory.item.number + " " + status); IssueDetailAdapter adapter = new IssueDetailAdapter(this, getLayoutInflater(), issueStory, issueInfo.repoInfo, this, this); recyclerView.setAdapter(adapter); invalidateOptionsMenu(); } private void changeColor(Issue issue) { int colorState = ContextCompat.getColor(this, R.color.issue_state_close); int colorStateDark = ContextCompat.getColor(this, R.color.issue_state_close_dark); if (IssueState.open == issue.state) { colorState = ContextCompat.getColor(this, R.color.issue_state_open); colorStateDark = ContextCompat.getColor(this, R.color.issue_state_open_dark); } View toolbarBg = findViewById(R.id.toolbarBg); if (toolbarBg != null) { toolbarBg.setBackgroundColor(colorState); } swipe.setColorSchemeColors(colorState); ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), primary, colorState); colorAnimation.addUpdateListener(animator -> { int color = (Integer) animator.getAnimatedValue(); if (getToolbar() != null) { getToolbar().setBackgroundColor(color); } }); ValueAnimator colorAnimationStatus = ValueAnimator.ofObject(new ArgbEvaluator(), primaryDark, colorStateDark); colorAnimationStatus.addUpdateListener(animator -> { int color = (Integer) animator.getAnimatedValue(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setStatusBarColor(color); } }); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(750); animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); animatorSet.playTogether(colorAnimation, colorAnimationStatus); final int finalColorState = colorState; final int finalColorStateDark = colorStateDark; animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { primary = finalColorState; primaryDark = finalColorStateDark; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animatorSet.start(); } public void showError() { hideProgressDialog(); MaterialDialog.Builder builder = new DialogUtils().builder(this); 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((dialog1, which) -> getContent()); builder.onNegative((dialog1, which) -> finish()); dialog = builder.show(); } @Override public void onClick(View view) { if (view.getId() == R.id.addCommentButton) { addComment(addCommentEditText.getText().toString()); addCommentEditText.setText(""); } else { String hint = getString(R.string.add_comment); Intent intent = ContentEditorActivity.createLauncherIntent(this, issueInfo.repoInfo, issueInfo.num, hint, addCommentEditText.getText().toString(), false, false); startActivityForResult(intent, NEW_COMMENT_REQUEST); } } @Override public boolean onCreateOptionsMenu(Menu menu) { if (this.issueStory != null) { if (issueInfo.repoInfo.permissions != null && issueInfo.repoInfo.permissions.push) { getMenuInflater().inflate(R.menu.issue_detail, menu); } else { getMenuInflater().inflate(R.menu.issue_detail_no_permissions, menu); } MenuItem item = menu.findItem(R.id.share_issue); item.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_menu_share_mtrl_alpha)); } return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (this.issueStory != null) { if (issueInfo.repoInfo.permissions != null && issueInfo.repoInfo.permissions.push || (accountNameProvider.getName() != null && accountNameProvider.getName().equals(issueStory.item.user.getLogin()))) { if (menu.findItem(R.id.action_close_issue) != null) { menu.removeItem(R.id.action_close_issue); } if (menu.findItem(R.id.action_reopen_issue) != null) { menu.removeItem(R.id.action_reopen_issue); } if (issueStory.item.state == IssueState.closed) { MenuItem menuItem = menu.add(0, R.id.action_reopen_issue, 1, getString(R.string.reopenIssue)); menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } else { MenuItem menuItem = menu.add(0, R.id.action_close_issue, 1, getString(R.string.closeIssue)); menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } } } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch (item.getItemId()) { case android.R.id.home: setResult(shouldRefreshOnBack ? RESULT_FIRST_USER : RESULT_OK); finish(); break; case R.id.action_close_issue: new CloseAction(this, issueInfo, R.string.closeIssue).setCallback(issue -> { getContent(); Snackbar.make(recyclerView, R.string.issue_change, Snackbar.LENGTH_SHORT).show(); }).execute(); break; case R.id.action_reopen_issue: new ReopenAction(this, issueInfo, R.string.reopenIssue).setCallback(issue -> { getContent(); Snackbar.make(recyclerView, R.string.issue_change, Snackbar.LENGTH_SHORT).show(); }).execute(); break; case R.id.issue_edit_milestone: editMilestone(); break; case R.id.issue_edit_assignee: new ChangeAssigneeAction(this, apiComponent, issueStory.item.assignees, issueInfo).setCallback(new AssigneActionCallback()) .execute(); break; case R.id.issue_edit_labels: openLabels(); break; case R.id.share_issue: if (issueStory != null && issueStory.item != null) { String title = issueInfo.toString(); String url = issueStory.item.getHtmlUrl(); new ShareAction(this, title, url).setType("Issue").execute(); } break; case R.id.action_add_shortcut: ShortcutUtils.addShortcut(this, issueInfo); break; } return true; } private void editMilestone() { GetMilestonesClient milestonesClient = new GetMilestonesClient(issueInfo.repoInfo, MilestoneState.open, true); milestonesClient.observable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new MilestonesCallback()); showProgressDialog(R.string.loading_milestones); } @Override public void onTitleEditRequest() { dialog = new DialogUtils().builder(this) .title(R.string.edit_issue_title) .input(null, issueStory.item.title, false, new MaterialDialog.InputCallback() { @Override public void onInput(MaterialDialog materialDialog, CharSequence charSequence) { EditIssueTitleRequestDTO editIssueTitleRequestDTO = new EditIssueTitleRequestDTO(); editIssueTitleRequestDTO.title = charSequence.toString(); executeEditIssue(editIssueTitleRequestDTO, R.string.issue_change_title); } }) .positiveText(R.string.edit_issue_button_ok) .neutralText(R.string.edit_issue_button_neutral) .show(); } @Override public void onContentEditRequest() { String body = issueStory.item.body != null ? issueStory.item.body.replace("\n", "<br />") : ""; Intent launcherIntent = ContentEditorActivity.createLauncherIntent(this, issueInfo.repoInfo, issueInfo.num, getString(R.string.edit_issue_body_hint), body, true, false); startActivityForResult(launcherIntent, ISSUE_BODY_EDIT); } private void showCreateMilestone() { MaterialDialog.Builder builder = new DialogUtils().builder(this); builder.title(R.string.add_milestone); builder.content(R.string.add_milestone_description); builder.input(R.string.add_milestone_hint, 0, new MaterialDialog.InputCallback() { @Override public void onInput(MaterialDialog materialDialog, CharSequence milestoneName) { createMilestone(milestoneName.toString()); } }).negativeText(R.string.cancel); builder.show(); } private void createMilestone(String milestoneName) { CreateMilestoneRequestDTO createMilestoneRequestDTO = new CreateMilestoneRequestDTO(milestoneName); CreateMilestoneClient createMilestoneClient = new CreateMilestoneClient(issueInfo.repoInfo, createMilestoneRequestDTO); createMilestoneClient.observable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Milestone>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Milestone milestone) { addMilestone(milestone); } }); } private void addMilestone(Milestone milestone) { showProgressDialog(R.string.add_milestone); EditIssueMilestoneRequestDTO editIssueRequestDTO = new EditIssueMilestoneRequestDTO(); editIssueRequestDTO.milestone = milestone.number; executeEditIssue(editIssueRequestDTO, R.string.issue_change_add_milestone); } private void addComment(String body) { showProgressDialog(R.string.adding_comment); AddIssueCommentAction addIssueCommentAction = getAddIssueCommentAction(body); addIssueCommentAction.setAddCommentCallback(new CommentCallback(body)); addIssueCommentAction.execute(); } private void clearMilestone() { showProgressDialog(R.string.clear_milestone); EditIssueMilestoneRequestDTO editIssueRequestDTO = new EditIssueMilestoneRequestDTO(); editIssueRequestDTO.milestone = null; executeEditIssue(editIssueRequestDTO, R.string.issue_change_clear_milestone); } private void executeEditIssue(final EditIssueRequestDTO editIssueRequestDTO, final int changedText) { EditIssueClient client = new EditIssueClient(issueInfo, editIssueRequestDTO); client.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<Issue>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Issue issue) { shouldRefreshOnBack = true; hideProgressDialog(); getContent(); Snackbar.make(recyclerView, changedText, Snackbar.LENGTH_SHORT).show(); } }); } /** * Labels */ private void openLabels() { GithubIssueLabelsClient labelsClient = new GithubIssueLabelsClient(issueInfo.repoInfo, true); labelsClient.observable().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new LabelsCallback()); } private void setLabels(CharSequence[] selectedLabels) { if (selectedLabels != null) { if (selectedLabels.length > 0) { String[] labels = new String[selectedLabels.length]; for (int i = 0; i < selectedLabels.length; i++) { labels[i] = selectedLabels[i].toString(); } EditIssueLabelsRequestDTO labelsRequestDTO = new EditIssueLabelsRequestDTO(); labelsRequestDTO.labels = labels; executeEditIssue(labelsRequestDTO, R.string.issue_change_labels); } else { EditIssueLabelsRequestDTO labelsRequestDTO = new EditIssueLabelsRequestDTO(); labelsRequestDTO.labels = new String[] {}; executeEditIssue(labelsRequestDTO, R.string.issue_change_labels_clear); } } else { EditIssueLabelsRequestDTO labelsRequestDTO = new EditIssueLabelsRequestDTO(); labelsRequestDTO.labels = new String[] {}; executeEditIssue(labelsRequestDTO, R.string.issue_change_labels_clear); } } @Override public void onBackPressed() { setResult(shouldRefreshOnBack ? RESULT_FIRST_USER : RESULT_OK); finish(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && data != null) { if (requestCode == NEW_COMMENT_REQUEST) { String body = data.getStringExtra(ContentEditorActivity.CONTENT); addComment(body); } else if (requestCode == ISSUE_BODY_EDIT) { EditIssueBodyRequestDTO bodyRequestDTO = new EditIssueBodyRequestDTO(); bodyRequestDTO.body = data.getStringExtra(ContentEditorActivity.CONTENT); executeEditIssue(bodyRequestDTO, R.string.issue_change_body); } else if (requestCode == EDIT_COMMENT_REQUEST) { IssueStoryComment comment = data.getParcelableExtra(ContentEditorActivity.PARCELABLE); String body = data.getStringExtra(ContentEditorActivity.CONTENT); editComment(comment, body); } } } @NonNull private AddIssueCommentAction getAddIssueCommentAction(String body) { return new AddIssueCommentAction(issueInfo, body); } @Override public void onRefresh() { getContent(); swipe.setOnRefreshListener(null); } @Override public void onContentEditRequest(IssueStoryComment issueStoryComment) { Intent intent = ContentEditorActivity.createLauncherIntent(this, null, issueStoryComment.comment.body, true, false); intent.putExtra(ContentEditorActivity.PARCELABLE, issueStoryComment); startActivityForResult(intent, EDIT_COMMENT_REQUEST); } private void editComment(IssueStoryComment issueStoryComment, String string) { RepoInfo repoInfo = new RepoInfo(); repoInfo.name = issueInfo.repoInfo.name; repoInfo.owner = issueInfo.repoInfo.owner; EditIssueCommentBodyRequest edit = new EditIssueCommentBodyRequest(repoInfo, issueStoryComment.comment.id, string); issueCommentPresenter.execute(edit); } @Override public void showLoading() { } @Override public void hideLoading() { } @Override public void onDataReceived(GithubComment data, boolean isFromPaginated) { getContent(); } @Override public void showError(Throwable throwable) { } private class MilestonesCallback implements Observer<List<Milestone>> { @Override public void onNext(final List<Milestone> milestones) { hideProgressDialog(); if (milestones.size() == 0) { showCreateMilestone(); } else { String[] itemsMilestones = new String[milestones.size()]; for (int i = 0; i < milestones.size(); i++) { itemsMilestones[i] = milestones.get(i).title; } int selectedMilestone = -1; for (int i = 0; i < milestones.size(); i++) { if (IssueDetailActivity.this.issueStory.item.milestone != null) { String currentMilestone = IssueDetailActivity.this.issueStory.item.milestone.title; if (currentMilestone != null && currentMilestone.equals(milestones.get(i).title)) { selectedMilestone = i; break; } } } MaterialDialog.Builder builder = new DialogUtils().builder(IssueDetailActivity.this) .title(R.string.select_milestone) .items(itemsMilestones) .itemsCallbackSingleChoice(selectedMilestone, (materialDialog, view, i, text) -> { addMilestone(milestones.get(i)); return false; }) .forceStacking(true) .widgetColor(AttributesUtils.getPrimaryColor(IssueDetailActivity.this)) .negativeText(R.string.add_milestone); if (selectedMilestone != -1) { builder.neutralText(R.string.clear_milestone); } builder.onNegative((dialog1, which) -> clearMilestone()); builder.onPositive((dialog1, which) -> showCreateMilestone()); builder.show(); } } @Override public void onError(Throwable error) { } @Override public void onCompleted() { } } private class LabelsCallback implements Observer<List<Label>> { private CharSequence[] selectedLabels; @Override public void onNext(List<Label> labels) { if (labels != null) { List<String> items = new ArrayList<>(); List<String> selectedLabels = new ArrayList<>(); List<Integer> positionsSelectedLabels = new ArrayList<>(); List<String> currentLabels = new ArrayList<>(); for (Label label : issueStory.item.labels) { currentLabels.add(label.name); } int i = 0; for (Label label : labels) { items.add(label.name); if (currentLabels.contains(label.name)) { selectedLabels.add(label.name); positionsSelectedLabels.add(i); } i++; } LabelsCallback.this.selectedLabels = selectedLabels.toArray(new String[selectedLabels.size()]); MaterialDialog.Builder builder = new DialogUtils().builder(IssueDetailActivity.this); builder.items(items); builder.alwaysCallMultiChoiceCallback(); builder.itemsCallbackMultiChoice(positionsSelectedLabels.toArray(new Integer[positionsSelectedLabels.size()]), (materialDialog, integers, charSequences) -> { LabelsCallback.this.selectedLabels = charSequences; return true; }); builder.stackingBehavior(StackingBehavior.ADAPTIVE); builder.positiveText(R.string.ok); builder.negativeText(R.string.clear_labels); builder.onPositive((dialog1, which) -> setLabels(LabelsCallback.this.selectedLabels)); builder.onNegative((dialog1, which) -> { LabelsCallback.this.selectedLabels = null; setLabels(null); }); dialog = builder.show(); } } @Override public void onError(Throwable error) { ErrorHandler.onError(IssueDetailActivity.this, "Issue detail", error); } @Override public void onCompleted() { } } private class CommentCallback implements AddIssueCommentAction.AddCommentCallback { private String body; private CommentCallback(String body) { this.body = body; } @Override public void onCommentAdded() { Snackbar.make(recyclerView, R.string.add_comment_issue_ok, Snackbar.LENGTH_SHORT).show(); addCommentEditText.setText(""); getContent(); } @Override public void onCommentError() { if (dialog != null) { dialog.dismiss(); } Snackbar.make(recyclerView, R.string.add_comment_issue_fail, Snackbar.LENGTH_SHORT).setAction(R.string.retry, v -> { addComment(body); }).show(); } @Override public void onCommentAddStarted() { } } private class AssigneActionCallback implements ActionCallback<Boolean> { @Override public void onResult(Boolean result) { if (result) { Snackbar.make(recyclerView, "Assignee changed", Snackbar.LENGTH_SHORT).show(); } else { Snackbar.make(recyclerView, "Assignee change failed", Snackbar.LENGTH_SHORT).show(); } getContent(); } } }