mirror of
https://github.com/gitnex-org/gitnex.git
synced 2026-05-07 19:36:42 -05:00
New Pr creation and edit UI
This commit is contained in:
@@ -39,10 +39,6 @@
|
||||
<activity
|
||||
android:name=".activities.AdminGetUsersActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
|
||||
<activity
|
||||
android:name=".activities.EditIssueActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
|
||||
android:windowSoftInputMode="adjustResize"/>
|
||||
<activity
|
||||
android:name=".activities.OrganizationTeamDetailsActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
|
||||
@@ -98,10 +94,6 @@
|
||||
<activity
|
||||
android:name=".activities.RepositorySettingsActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"/>
|
||||
<activity
|
||||
android:name=".activities.CreatePullRequestActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
|
||||
android:windowSoftInputMode="adjustResize"/>
|
||||
<activity
|
||||
android:name=".activities.CodeEditorActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
|
||||
|
||||
@@ -1,788 +0,0 @@
|
||||
package org.mian.gitnex.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.vdurmont.emoji.EmojiParser;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import org.gitnex.tea4j.v2.models.Attachment;
|
||||
import org.gitnex.tea4j.v2.models.Branch;
|
||||
import org.gitnex.tea4j.v2.models.CreatePullRequestOption;
|
||||
import org.gitnex.tea4j.v2.models.Label;
|
||||
import org.gitnex.tea4j.v2.models.Milestone;
|
||||
import org.gitnex.tea4j.v2.models.PullRequest;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.actions.LabelsActions;
|
||||
import org.mian.gitnex.adapters.AttachmentsAdapter;
|
||||
import org.mian.gitnex.adapters.BranchAdapter;
|
||||
import org.mian.gitnex.adapters.LabelsListAdapter;
|
||||
import org.mian.gitnex.adapters.NotesAdapter;
|
||||
import org.mian.gitnex.clients.RetrofitClient;
|
||||
import org.mian.gitnex.database.api.BaseApi;
|
||||
import org.mian.gitnex.database.api.NotesApi;
|
||||
import org.mian.gitnex.database.models.Notes;
|
||||
import org.mian.gitnex.databinding.ActivityCreatePrBinding;
|
||||
import org.mian.gitnex.databinding.BottomSheetAttachmentsBinding;
|
||||
import org.mian.gitnex.databinding.CustomInsertNoteBinding;
|
||||
import org.mian.gitnex.databinding.CustomLabelsSelectionDialogBinding;
|
||||
import org.mian.gitnex.helpers.AlertDialogs;
|
||||
import org.mian.gitnex.helpers.AppDatabaseSettings;
|
||||
import org.mian.gitnex.helpers.Constants;
|
||||
import org.mian.gitnex.helpers.Markdown;
|
||||
import org.mian.gitnex.helpers.MentionHelper;
|
||||
import org.mian.gitnex.helpers.Toasty;
|
||||
import org.mian.gitnex.helpers.attachments.AttachmentUtils;
|
||||
import org.mian.gitnex.helpers.attachments.AttachmentsModel;
|
||||
import org.mian.gitnex.helpers.contexts.RepositoryContext;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* @author mmarif
|
||||
*/
|
||||
public class CreatePullRequestActivity extends BaseActivity
|
||||
implements LabelsListAdapter.LabelsListAdapterListener,
|
||||
AttachmentsAdapter.AttachmentsReceiverListener {
|
||||
|
||||
private final List<String> assignees = new ArrayList<>();
|
||||
LinkedHashMap<String, Milestone> milestonesList = new LinkedHashMap<>();
|
||||
List<Label> labelsList = new ArrayList<>();
|
||||
private ActivityCreatePrBinding viewBinding;
|
||||
private List<Integer> labelsIds = new ArrayList<>();
|
||||
private int milestoneId;
|
||||
private RepositoryContext repository;
|
||||
private LabelsListAdapter labelsAdapter;
|
||||
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
|
||||
private MaterialAlertDialogBuilder materialAlertDialogBuilderNotes;
|
||||
private boolean renderMd = false;
|
||||
private RepositoryContext repositoryContext;
|
||||
private static List<AttachmentsModel> attachmentsList;
|
||||
private AttachmentsAdapter attachmentsAdapter;
|
||||
private static final List<Uri> contentUri = new ArrayList<>();
|
||||
private CustomInsertNoteBinding customInsertNoteBinding;
|
||||
private NotesAdapter adapter;
|
||||
private NotesApi notesApi;
|
||||
public AlertDialog dialogNotes;
|
||||
private MentionHelper mentionHelper;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding = ActivityCreatePrBinding.inflate(getLayoutInflater());
|
||||
setContentView(viewBinding.getRoot());
|
||||
|
||||
repositoryContext = RepositoryContext.fromIntent(getIntent());
|
||||
|
||||
materialAlertDialogBuilder =
|
||||
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
|
||||
materialAlertDialogBuilderNotes =
|
||||
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
|
||||
|
||||
repository = RepositoryContext.fromIntent(getIntent());
|
||||
|
||||
attachmentsList = new ArrayList<>();
|
||||
attachmentsAdapter = new AttachmentsAdapter(attachmentsList, ctx);
|
||||
|
||||
AttachmentsAdapter.setAttachmentsReceiveListener(this);
|
||||
|
||||
int resultLimit = Constants.getCurrentResultLimit(ctx);
|
||||
|
||||
mentionHelper = new MentionHelper(this, viewBinding.prBody);
|
||||
mentionHelper.setup();
|
||||
|
||||
viewBinding.prBody.setOnTouchListener(
|
||||
(touchView, motionEvent) -> {
|
||||
touchView.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
if ((motionEvent.getAction() & MotionEvent.ACTION_UP) != 0
|
||||
&& (motionEvent.getActionMasked() & MotionEvent.ACTION_UP) != 0) {
|
||||
|
||||
touchView.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
labelsAdapter =
|
||||
new LabelsListAdapter(labelsList, CreatePullRequestActivity.this, labelsIds);
|
||||
|
||||
showDatePickerDialog();
|
||||
|
||||
viewBinding.topAppBar.setNavigationOnClickListener(
|
||||
v -> {
|
||||
finish();
|
||||
contentUri.clear();
|
||||
});
|
||||
|
||||
viewBinding.topAppBar.setOnMenuItemClickListener(
|
||||
menuItem -> {
|
||||
int id = menuItem.getItemId();
|
||||
|
||||
if (id == R.id.markdown) {
|
||||
|
||||
if (!renderMd) {
|
||||
Markdown.render(
|
||||
ctx,
|
||||
EmojiParser.parseToUnicode(
|
||||
Objects.requireNonNull(viewBinding.prBody.getText())
|
||||
.toString()),
|
||||
viewBinding.markdownPreview,
|
||||
repositoryContext);
|
||||
|
||||
viewBinding.markdownPreview.setVisibility(View.VISIBLE);
|
||||
viewBinding.prBodyLayout.setVisibility(View.GONE);
|
||||
renderMd = true;
|
||||
} else {
|
||||
viewBinding.markdownPreview.setVisibility(View.GONE);
|
||||
viewBinding.prBodyLayout.setVisibility(View.VISIBLE);
|
||||
renderMd = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (id == R.id.create) {
|
||||
processPullRequest();
|
||||
return true;
|
||||
} else if (id == R.id.attachment) {
|
||||
checkForAttachments();
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.insertNote.setOnClickListener(insertNote -> showAllNotes());
|
||||
|
||||
getMilestones(repository.getOwner(), repository.getName(), resultLimit);
|
||||
|
||||
viewBinding.mergeIntoBranchSpinner.setKeyListener(null);
|
||||
viewBinding.mergeIntoBranchSpinner.setCursorVisible(false);
|
||||
|
||||
viewBinding.mergeIntoBranchSpinner.setOnFocusChangeListener(
|
||||
(v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
getBranches("merge");
|
||||
viewBinding.mergeIntoBranchSpinner.clearFocus();
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.pullFromBranchSpinner.setKeyListener(null);
|
||||
viewBinding.pullFromBranchSpinner.setCursorVisible(false);
|
||||
|
||||
viewBinding.pullFromBranchSpinner.setOnFocusChangeListener(
|
||||
(v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
getBranches("pull");
|
||||
viewBinding.pullFromBranchSpinner.clearFocus();
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.prLabels.setOnClickListener(prLabels -> showLabels());
|
||||
|
||||
if (!repository.getPermissions().isPush()) {
|
||||
viewBinding.prDueDateLayout.setVisibility(View.GONE);
|
||||
viewBinding.prLabelsLayout.setVisibility(View.GONE);
|
||||
viewBinding.milestonesSpinnerLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
ActivityResultLauncher<Intent> startActivityForResult =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
Intent data = result.getData();
|
||||
assert data != null;
|
||||
contentUri.add(data.getData());
|
||||
attachmentsList.add(
|
||||
new AttachmentsModel(
|
||||
AttachmentUtils.queryName(ctx, data.getData()),
|
||||
data.getData()));
|
||||
attachmentsAdapter.updateList(attachmentsList);
|
||||
}
|
||||
});
|
||||
|
||||
private void showAllNotes() {
|
||||
List<Notes> notesList = new ArrayList<>();
|
||||
notesApi = BaseApi.getInstance(ctx, NotesApi.class);
|
||||
customInsertNoteBinding = CustomInsertNoteBinding.inflate(LayoutInflater.from(ctx));
|
||||
|
||||
materialAlertDialogBuilderNotes.setView(customInsertNoteBinding.getRoot());
|
||||
|
||||
customInsertNoteBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
|
||||
adapter = new NotesAdapter(ctx, notesList, "insert", "pr");
|
||||
customInsertNoteBinding.recyclerView.setAdapter(adapter);
|
||||
|
||||
if (notesApi.getCount() > 0) {
|
||||
fetchNotes();
|
||||
dialogNotes = materialAlertDialogBuilderNotes.show();
|
||||
} else {
|
||||
Toasty.show(ctx, getString(R.string.noNotes));
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNotes() {
|
||||
customInsertNoteBinding.expressiveLoader.setVisibility(View.VISIBLE);
|
||||
|
||||
notesApi.fetchAllNotes()
|
||||
.observe(
|
||||
this,
|
||||
allNotes -> {
|
||||
customInsertNoteBinding.expressiveLoader.setVisibility(View.GONE);
|
||||
|
||||
if (allNotes != null && !allNotes.isEmpty()) {
|
||||
adapter.updateList(allNotes);
|
||||
customInsertNoteBinding
|
||||
.layoutEmpty
|
||||
.getRoot()
|
||||
.setVisibility(View.GONE);
|
||||
} else {
|
||||
adapter.updateList(new ArrayList<>());
|
||||
customInsertNoteBinding
|
||||
.layoutEmpty
|
||||
.getRoot()
|
||||
.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
AttachmentsAdapter.setAttachmentsReceiveListener(null);
|
||||
mentionHelper.dismissPopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttachmentsData(Uri filename) {
|
||||
contentUri.remove(filename);
|
||||
}
|
||||
|
||||
private void checkForAttachments() {
|
||||
|
||||
if (!contentUri.isEmpty()) {
|
||||
|
||||
BottomSheetAttachmentsBinding bottomSheetAttachmentsBinding =
|
||||
BottomSheetAttachmentsBinding.inflate(getLayoutInflater());
|
||||
|
||||
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(ctx);
|
||||
|
||||
bottomSheetAttachmentsBinding.addAttachment.setOnClickListener(
|
||||
v1 -> openFileAttachmentActivity());
|
||||
|
||||
bottomSheetAttachmentsBinding.recyclerViewAttachments.setHasFixedSize(true);
|
||||
bottomSheetAttachmentsBinding.recyclerViewAttachments.setLayoutManager(
|
||||
new LinearLayoutManager(ctx));
|
||||
bottomSheetAttachmentsBinding.recyclerViewAttachments.setAdapter(attachmentsAdapter);
|
||||
|
||||
bottomSheetDialog.setContentView(bottomSheetAttachmentsBinding.getRoot());
|
||||
bottomSheetDialog.show();
|
||||
} else {
|
||||
openFileAttachmentActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private void openFileAttachmentActivity() {
|
||||
|
||||
Intent data = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
data.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
data.setType("*/*");
|
||||
Intent intent = Intent.createChooser(data, "Choose a file");
|
||||
startActivityForResult.launch(intent);
|
||||
}
|
||||
|
||||
private void processAttachments(long issueIndex) {
|
||||
|
||||
for (int i = 0; i < contentUri.size(); i++) {
|
||||
|
||||
File file = AttachmentUtils.getFile(ctx, contentUri.get(i));
|
||||
|
||||
RequestBody requestFile =
|
||||
RequestBody.create(
|
||||
file,
|
||||
MediaType.parse(
|
||||
Objects.requireNonNull(
|
||||
getContentResolver().getType(contentUri.get(i)))));
|
||||
|
||||
uploadAttachments(requestFile, issueIndex, file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadAttachments(RequestBody requestFile, long issueIndex, String filename1) {
|
||||
|
||||
Call<Attachment> call3 =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueCreateIssueAttachment(
|
||||
requestFile,
|
||||
repository.getOwner(),
|
||||
repository.getName(),
|
||||
issueIndex,
|
||||
filename1);
|
||||
|
||||
call3.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<Attachment> call,
|
||||
@NonNull retrofit2.Response<Attachment> response2) {
|
||||
|
||||
if (response2.code() == 201) {
|
||||
new Handler().postDelayed(() -> finish(), 3000);
|
||||
} else if (response2.code() == 401) {
|
||||
|
||||
AlertDialogs.authorizationTokenRevokedDialog(ctx);
|
||||
} else {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.attachmentsSaveError));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.genericServerResponseError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processPullRequest() {
|
||||
|
||||
String prTitle = String.valueOf(viewBinding.prTitle.getText());
|
||||
String prDescription = String.valueOf(viewBinding.prBody.getText());
|
||||
String mergeInto =
|
||||
Objects.requireNonNull(viewBinding.mergeIntoBranchSpinner.getText()).toString();
|
||||
String pullFrom =
|
||||
Objects.requireNonNull(viewBinding.pullFromBranchSpinner.getText()).toString();
|
||||
String prDueDate = Objects.requireNonNull(viewBinding.prDueDate.getText()).toString();
|
||||
|
||||
assignees.add("");
|
||||
|
||||
if (labelsIds.isEmpty()) {
|
||||
|
||||
labelsIds.add(0);
|
||||
}
|
||||
|
||||
if (prTitle.matches("")) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.titleError));
|
||||
} else if (mergeInto.matches("")) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.mergeIntoError));
|
||||
} else if (pullFrom.matches("")) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.pullFromError));
|
||||
} else if (pullFrom.equals(mergeInto)) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.sameBranchesError));
|
||||
} else {
|
||||
|
||||
createPullRequest(
|
||||
prTitle, prDescription, mergeInto, pullFrom, milestoneId, assignees, prDueDate);
|
||||
}
|
||||
}
|
||||
|
||||
private void createPullRequest(
|
||||
String prTitle,
|
||||
String prDescription,
|
||||
String mergeInto,
|
||||
String pullFrom,
|
||||
int milestoneId,
|
||||
List<String> assignees,
|
||||
String prDueDate) {
|
||||
|
||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false);
|
||||
|
||||
ArrayList<Long> labelIds = new ArrayList<>();
|
||||
for (Integer i : labelsIds) {
|
||||
labelIds.add((long) i);
|
||||
}
|
||||
|
||||
CreatePullRequestOption createPullRequest = new CreatePullRequestOption();
|
||||
createPullRequest.setTitle(prTitle);
|
||||
createPullRequest.setMilestone((long) milestoneId);
|
||||
createPullRequest.setAssignees(assignees);
|
||||
createPullRequest.setBody(prDescription);
|
||||
createPullRequest.setBase(mergeInto);
|
||||
createPullRequest.setHead(pullFrom);
|
||||
createPullRequest.setLabels(labelIds);
|
||||
String[] date = prDueDate.split("-");
|
||||
if (!prDueDate.equalsIgnoreCase("")) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.YEAR, Integer.parseInt(date[0]));
|
||||
calendar.set(Calendar.MONTH, Integer.parseInt(date[1]));
|
||||
calendar.set(Calendar.DATE, Integer.parseInt(date[2]));
|
||||
Date dueDate = calendar.getTime();
|
||||
createPullRequest.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
Call<PullRequest> transferCall =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.repoCreatePullRequest(
|
||||
repository.getOwner(), repository.getName(), createPullRequest);
|
||||
|
||||
transferCall.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<PullRequest> call,
|
||||
@NonNull retrofit2.Response<PullRequest> response) {
|
||||
|
||||
if (response.code() == 201) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.prCreateSuccess));
|
||||
// RepoDetailActivity.updateRepo = true;
|
||||
// PullRequestsFragment.resumePullRequests = true;
|
||||
MainActivity.reloadRepos = true;
|
||||
|
||||
if (!contentUri.isEmpty()) {
|
||||
assert response.body() != null;
|
||||
processAttachments(response.body().getNumber());
|
||||
contentUri.clear();
|
||||
} else {
|
||||
new Handler().postDelayed(() -> finish(), 3000);
|
||||
}
|
||||
} else if (response.code() == 409
|
||||
|| response.message().equals("Conflict")) {
|
||||
|
||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false);
|
||||
Toasty.show(ctx, getString(R.string.prAlreadyExists));
|
||||
} else if (response.code() == 404) {
|
||||
|
||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false);
|
||||
Toasty.show(ctx, getString(R.string.apiNotFound));
|
||||
} else {
|
||||
|
||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false);
|
||||
Toasty.show(ctx, getString(R.string.genericError));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<PullRequest> call, @NonNull Throwable t) {
|
||||
|
||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false);
|
||||
Toasty.show(ctx, getString(R.string.genericServerResponseError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showDatePickerDialog() {
|
||||
|
||||
MaterialDatePicker.Builder<Long> builder = MaterialDatePicker.Builder.datePicker();
|
||||
builder.setSelection(Calendar.getInstance().getTimeInMillis());
|
||||
builder.setTitleText(R.string.newIssueDueDateTitle);
|
||||
MaterialDatePicker<Long> materialDatePicker = builder.build();
|
||||
|
||||
String[] locale_ =
|
||||
AppDatabaseSettings.getSettingsValue(ctx, AppDatabaseSettings.APP_LOCALE_KEY)
|
||||
.split("\\|");
|
||||
|
||||
viewBinding.prDueDate.setOnClickListener(
|
||||
v -> materialDatePicker.show(getSupportFragmentManager(), "DATE_PICKER"));
|
||||
|
||||
materialDatePicker.addOnPositiveButtonClickListener(
|
||||
selection -> {
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
calendar.setTimeInMillis(selection);
|
||||
SimpleDateFormat format =
|
||||
new SimpleDateFormat("yyyy-MM-dd", new Locale(locale_[1]));
|
||||
String formattedDate = format.format(calendar.getTime());
|
||||
viewBinding.prDueDate.setText(formattedDate);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void labelsInterface(List<String> data) {
|
||||
|
||||
String labelsSetter = String.valueOf(data);
|
||||
viewBinding.prLabels.setText(labelsSetter.replace("]", "").replace("[", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void labelsIdsInterface(List<Integer> data) {
|
||||
|
||||
labelsIds = data;
|
||||
}
|
||||
|
||||
private void showLabels() {
|
||||
|
||||
viewBinding.progressBar.setVisibility(View.VISIBLE);
|
||||
CustomLabelsSelectionDialogBinding labelsBinding =
|
||||
CustomLabelsSelectionDialogBinding.inflate(LayoutInflater.from(ctx));
|
||||
View view = labelsBinding.getRoot();
|
||||
materialAlertDialogBuilder.setView(view);
|
||||
|
||||
materialAlertDialogBuilder.setNeutralButton(R.string.close, null);
|
||||
LabelsActions.getRepositoryLabels(
|
||||
ctx,
|
||||
repository.getOwner(),
|
||||
repository.getName(),
|
||||
labelsList,
|
||||
materialAlertDialogBuilder,
|
||||
labelsAdapter,
|
||||
labelsBinding,
|
||||
viewBinding.progressBar);
|
||||
}
|
||||
|
||||
private void getBranches(String type) {
|
||||
|
||||
Dialog progressDialog = new Dialog(ctx);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setContentView(R.layout.custom_progress_loader);
|
||||
progressDialog.show();
|
||||
|
||||
MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(ctx);
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.custom_branches_dialog, null);
|
||||
dialogBuilder.setView(dialogView);
|
||||
|
||||
RecyclerView recyclerView = dialogView.findViewById(R.id.recyclerView);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
|
||||
|
||||
recyclerView.addItemDecoration(
|
||||
new RecyclerView.ItemDecoration() {
|
||||
@Override
|
||||
public void getItemOffsets(
|
||||
@NonNull Rect outRect,
|
||||
@NonNull View view,
|
||||
@NonNull RecyclerView parent,
|
||||
@NonNull RecyclerView.State state) {
|
||||
|
||||
int position = parent.getChildAdapterPosition(view);
|
||||
int spacingSides = (int) ctx.getResources().getDimension(R.dimen.dimen16dp);
|
||||
int spacingTop = (int) ctx.getResources().getDimension(R.dimen.dimen12dp);
|
||||
|
||||
outRect.right = spacingSides;
|
||||
outRect.left = spacingSides;
|
||||
|
||||
if (position > 0) {
|
||||
outRect.top = spacingTop;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dialogBuilder.setNeutralButton(R.string.close, (dialog, which) -> dialog.dismiss());
|
||||
AlertDialog dialog = dialogBuilder.create();
|
||||
dialog.setCancelable(false);
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
|
||||
final int[] page = {1};
|
||||
final int resultLimit = Constants.getCurrentResultLimit(ctx);
|
||||
final boolean[] isLoading = {false};
|
||||
final boolean[] isLastPage = {false};
|
||||
|
||||
BranchAdapter adapter =
|
||||
new BranchAdapter(
|
||||
branchName -> {
|
||||
if (type.equalsIgnoreCase("merge")) {
|
||||
viewBinding.mergeIntoBranchSpinner.setText(branchName);
|
||||
}
|
||||
if (type.equalsIgnoreCase("pull")) {
|
||||
viewBinding.pullFromBranchSpinner.setText(branchName);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
Runnable fetchBranches =
|
||||
() -> {
|
||||
if (isLoading[0] || isLastPage[0]) return;
|
||||
isLoading[0] = true;
|
||||
|
||||
Call<List<Branch>> call =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.repoListBranches(
|
||||
repository.getOwner(),
|
||||
repository.getName(),
|
||||
page[0],
|
||||
resultLimit);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<List<Branch>> call,
|
||||
@NonNull Response<List<Branch>> response) {
|
||||
|
||||
isLoading[0] = false;
|
||||
|
||||
if (response.code() == 200 && response.body() != null) {
|
||||
List<Branch> newBranches = response.body();
|
||||
adapter.addBranches(newBranches);
|
||||
|
||||
String totalCountStr =
|
||||
response.headers().get("X-Total-Count");
|
||||
|
||||
if (totalCountStr != null) {
|
||||
|
||||
int totalItems = Integer.parseInt(totalCountStr);
|
||||
int totalPages =
|
||||
(int)
|
||||
Math.ceil(
|
||||
(double) totalItems
|
||||
/ resultLimit);
|
||||
isLastPage[0] = page[0] >= totalPages;
|
||||
} else {
|
||||
isLastPage[0] = newBranches.size() < resultLimit;
|
||||
}
|
||||
page[0]++;
|
||||
|
||||
if (page[0] == 2 && !dialog.isShowing()) {
|
||||
progressDialog.dismiss();
|
||||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(
|
||||
@NonNull Call<List<Branch>> call, @NonNull Throwable t) {
|
||||
isLoading[0] = false;
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
recyclerView.addOnScrollListener(
|
||||
new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
LinearLayoutManager layoutManager =
|
||||
(LinearLayoutManager) recyclerView.getLayoutManager();
|
||||
|
||||
if (layoutManager != null) {
|
||||
|
||||
int visibleItemCount = layoutManager.getChildCount();
|
||||
int totalItemCount = layoutManager.getItemCount();
|
||||
int firstVisibleItemPosition =
|
||||
layoutManager.findFirstVisibleItemPosition();
|
||||
|
||||
if (!isLoading[0]
|
||||
&& !isLastPage[0]
|
||||
&& (visibleItemCount + firstVisibleItemPosition)
|
||||
>= totalItemCount - 5) {
|
||||
fetchBranches.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// adapter.clear();
|
||||
fetchBranches.run();
|
||||
}
|
||||
|
||||
private void getMilestones(String repoOwner, String repoName, int resultLimit) {
|
||||
|
||||
String msState = "open";
|
||||
Call<List<Milestone>> call =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueGetMilestonesList(repoOwner, repoName, msState, null, 1, resultLimit);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<List<Milestone>> call,
|
||||
@NonNull retrofit2.Response<List<Milestone>> response) {
|
||||
|
||||
if (response.code() == 200) {
|
||||
|
||||
List<Milestone> milestonesList_ = response.body();
|
||||
|
||||
milestonesList.put(
|
||||
getString(R.string.issueCreatedNoMilestone),
|
||||
new Milestone()
|
||||
.id(0L)
|
||||
.title(getString(R.string.issueCreatedNoMilestone)));
|
||||
assert milestonesList_ != null;
|
||||
|
||||
if (!milestonesList_.isEmpty()) {
|
||||
|
||||
for (Milestone milestone : milestonesList_) {
|
||||
|
||||
// Don't translate "open" is a enum
|
||||
if (milestone.getState().equals("open")) {
|
||||
milestonesList.put(milestone.getTitle(), milestone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<>(
|
||||
CreatePullRequestActivity.this,
|
||||
R.layout.list_spinner_items,
|
||||
new ArrayList<>(milestonesList.keySet()));
|
||||
|
||||
viewBinding.milestonesSpinner.setAdapter(adapter);
|
||||
|
||||
viewBinding.milestonesSpinner.setOnItemClickListener(
|
||||
(parent, view, position, id) -> {
|
||||
if (position == 0) {
|
||||
milestoneId = 0;
|
||||
} else if (view instanceof TextView) {
|
||||
milestoneId =
|
||||
Math.toIntExact(
|
||||
Objects.requireNonNull(
|
||||
milestonesList.get(
|
||||
((TextView)
|
||||
view)
|
||||
.getText()
|
||||
.toString()))
|
||||
.getId());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(
|
||||
@NonNull Call<List<Milestone>> call, @NonNull Throwable t) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.genericServerResponseError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
repository.checkAccountSwitch(this);
|
||||
}
|
||||
}
|
||||
@@ -1,894 +0,0 @@
|
||||
package org.mian.gitnex.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.vdurmont.emoji.EmojiParser;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.gitnex.tea4j.v2.models.Attachment;
|
||||
import org.gitnex.tea4j.v2.models.EditIssueOption;
|
||||
import org.gitnex.tea4j.v2.models.Issue;
|
||||
import org.gitnex.tea4j.v2.models.Milestone;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.adapters.AttachmentsAdapter;
|
||||
import org.mian.gitnex.clients.RetrofitClient;
|
||||
import org.mian.gitnex.databinding.ActivityEditIssueBinding;
|
||||
import org.mian.gitnex.databinding.BottomSheetAttachmentsBinding;
|
||||
import org.mian.gitnex.databinding.CustomImageViewDialogBinding;
|
||||
import org.mian.gitnex.helpers.AlertDialogs;
|
||||
import org.mian.gitnex.helpers.AppDatabaseSettings;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.Constants;
|
||||
import org.mian.gitnex.helpers.Markdown;
|
||||
import org.mian.gitnex.helpers.MentionHelper;
|
||||
import org.mian.gitnex.helpers.Toasty;
|
||||
import org.mian.gitnex.helpers.attachments.AttachmentUtils;
|
||||
import org.mian.gitnex.helpers.attachments.AttachmentsModel;
|
||||
import org.mian.gitnex.helpers.contexts.IssueContext;
|
||||
import org.mian.gitnex.notifications.Notifications;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* @author mmarif
|
||||
*/
|
||||
public class EditIssueActivity extends BaseActivity
|
||||
implements AttachmentsAdapter.AttachmentsReceiverListener {
|
||||
|
||||
private ActivityEditIssueBinding binding;
|
||||
private final String msState = "open";
|
||||
private final LinkedHashMap<String, Milestone> milestonesList = new LinkedHashMap<>();
|
||||
private int milestoneId = 0;
|
||||
private IssueContext issue;
|
||||
private boolean renderMd = false;
|
||||
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
|
||||
private String token;
|
||||
private String filename;
|
||||
private Long filesize;
|
||||
private String filehash;
|
||||
private String instanceUrlOnly;
|
||||
private AttachmentsAdapter attachmentsAdapter;
|
||||
private static List<AttachmentsModel> attachmentsList;
|
||||
private static final List<Uri> contentUri = new ArrayList<>();
|
||||
private MenuItem create;
|
||||
private MentionHelper mentionHelper;
|
||||
|
||||
public ActivityResultLauncher<Intent> downloadAttachmentLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
|
||||
assert result.getData() != null;
|
||||
|
||||
try {
|
||||
|
||||
OutputStream outputStream =
|
||||
getContentResolver()
|
||||
.openOutputStream(
|
||||
Objects.requireNonNull(
|
||||
result.getData().getData()));
|
||||
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(ctx, ctx.getPackageName())
|
||||
.setContentTitle(
|
||||
getString(
|
||||
R.string
|
||||
.fileViewerNotificationTitleStarted))
|
||||
.setContentText(
|
||||
getString(
|
||||
R.string
|
||||
.fileViewerNotificationDescriptionStarted,
|
||||
filename))
|
||||
.setSmallIcon(R.drawable.gitnex_transparent)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setChannelId(
|
||||
Constants.downloadNotificationChannelId)
|
||||
.setProgress(100, 0, false)
|
||||
.setOngoing(true);
|
||||
|
||||
int notificationId = Notifications.uniqueNotificationId(ctx);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager)
|
||||
getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
|
||||
Thread thread =
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
|
||||
Call<ResponseBody> call =
|
||||
RetrofitClient.getWebInterface(
|
||||
ctx,
|
||||
instanceUrlOnly)
|
||||
.getAttachment(filehash);
|
||||
|
||||
Response<ResponseBody> response =
|
||||
call.execute();
|
||||
|
||||
assert response.body() != null;
|
||||
|
||||
builder.setOngoing(false)
|
||||
.setContentTitle(
|
||||
getString(
|
||||
R.string
|
||||
.fileViewerNotificationTitleFinished))
|
||||
.setContentText(
|
||||
getString(
|
||||
R.string
|
||||
.fileViewerNotificationDescriptionFinished,
|
||||
filename));
|
||||
|
||||
AppUtil.copyProgress(
|
||||
response.body().byteStream(),
|
||||
outputStream,
|
||||
filesize,
|
||||
progress -> {
|
||||
builder.setProgress(
|
||||
100, progress, false);
|
||||
notificationManager.notify(
|
||||
notificationId,
|
||||
builder.build());
|
||||
});
|
||||
|
||||
} catch (IOException ignored) {
|
||||
|
||||
builder.setOngoing(false)
|
||||
.setContentTitle(
|
||||
getString(
|
||||
R.string
|
||||
.fileViewerNotificationTitleFailed))
|
||||
.setContentText(
|
||||
getString(
|
||||
R.string
|
||||
.fileViewerNotificationDescriptionFailed,
|
||||
filename));
|
||||
|
||||
} finally {
|
||||
|
||||
builder.setProgress(0, 0, false)
|
||||
.setOngoing(false);
|
||||
|
||||
notificationManager.notify(
|
||||
notificationId, builder.build());
|
||||
}
|
||||
});
|
||||
|
||||
thread.start();
|
||||
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActivityResultLauncher<Intent> startActivityForResult =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
Intent data = result.getData();
|
||||
assert data != null;
|
||||
contentUri.add(data.getData());
|
||||
attachmentsList.add(
|
||||
new AttachmentsModel(
|
||||
AttachmentUtils.queryName(ctx, data.getData()),
|
||||
data.getData()));
|
||||
attachmentsAdapter.updateList(attachmentsList);
|
||||
}
|
||||
});
|
||||
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
AttachmentsAdapter.setAttachmentsReceiveListener(null);
|
||||
mentionHelper.dismissPopup();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = ActivityEditIssueBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
int resultLimit = Constants.getCurrentResultLimit(ctx);
|
||||
issue = IssueContext.fromIntent(getIntent());
|
||||
|
||||
binding.topAppBar.setNavigationOnClickListener(
|
||||
v -> {
|
||||
finish();
|
||||
contentUri.clear();
|
||||
});
|
||||
|
||||
materialAlertDialogBuilder =
|
||||
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
|
||||
|
||||
token = ((BaseActivity) ctx).getAccount().getAccount().getToken();
|
||||
|
||||
String instanceUrl = ((BaseActivity) ctx).getAccount().getAccount().getInstanceUrl();
|
||||
instanceUrlOnly = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/"));
|
||||
|
||||
attachmentsList = new ArrayList<>();
|
||||
attachmentsAdapter = new AttachmentsAdapter(attachmentsList, ctx);
|
||||
|
||||
AttachmentsAdapter.setAttachmentsReceiveListener(this);
|
||||
|
||||
mentionHelper = new MentionHelper(this, binding.editIssueDescription);
|
||||
mentionHelper.setup();
|
||||
|
||||
create = binding.topAppBar.getMenu().getItem(2);
|
||||
create.setTitle(getString(R.string.saveButton));
|
||||
|
||||
binding.editIssueDescription.setOnTouchListener(
|
||||
(touchView, motionEvent) -> {
|
||||
touchView.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
if ((motionEvent.getAction() & MotionEvent.ACTION_UP) != 0
|
||||
&& (motionEvent.getActionMasked() & MotionEvent.ACTION_UP) != 0) {
|
||||
|
||||
touchView.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (issue.getIssueType().equalsIgnoreCase("Pull")) {
|
||||
binding.topAppBar.setTitle(
|
||||
getString(R.string.editPrNavHeader, String.valueOf(issue.getIssueIndex())));
|
||||
} else {
|
||||
binding.topAppBar.setTitle(
|
||||
getString(R.string.editIssueNavHeader, String.valueOf(issue.getIssueIndex())));
|
||||
}
|
||||
|
||||
showDatePickerDialog();
|
||||
|
||||
binding.topAppBar.setOnMenuItemClickListener(
|
||||
menuItem -> {
|
||||
int id = menuItem.getItemId();
|
||||
|
||||
if (id == R.id.markdown) {
|
||||
|
||||
if (!renderMd) {
|
||||
|
||||
Markdown.render(
|
||||
ctx,
|
||||
EmojiParser.parseToUnicode(
|
||||
Objects.requireNonNull(
|
||||
binding.editIssueDescription.getText())
|
||||
.toString()),
|
||||
binding.markdownPreview,
|
||||
issue.getRepository());
|
||||
|
||||
binding.markdownPreview.setVisibility(View.VISIBLE);
|
||||
binding.editIssueDescriptionLayout.setVisibility(View.GONE);
|
||||
renderMd = true;
|
||||
} else {
|
||||
binding.markdownPreview.setVisibility(View.GONE);
|
||||
binding.editIssueDescriptionLayout.setVisibility(View.VISIBLE);
|
||||
renderMd = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (id == R.id.create) {
|
||||
create.setVisible(false);
|
||||
processEditIssue();
|
||||
if (!contentUri.isEmpty()) {
|
||||
processAttachments();
|
||||
contentUri.clear();
|
||||
}
|
||||
return true;
|
||||
} else if (id == R.id.attachment) {
|
||||
checkForAttachments();
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
});
|
||||
|
||||
getIssue(
|
||||
issue.getRepository().getOwner(),
|
||||
issue.getRepository().getName(),
|
||||
issue.getIssueIndex(),
|
||||
resultLimit);
|
||||
|
||||
getAttachments();
|
||||
|
||||
if (!issue.getRepository().getPermissions().isPush()) {
|
||||
findViewById(R.id.editIssueMilestoneSpinnerLayout).setVisibility(View.GONE);
|
||||
findViewById(R.id.editIssueDueDateLayout).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttachmentsData(Uri filename) {
|
||||
contentUri.remove(filename);
|
||||
}
|
||||
|
||||
private void checkForAttachments() {
|
||||
|
||||
if (!contentUri.isEmpty()) {
|
||||
|
||||
BottomSheetAttachmentsBinding bottomSheetAttachmentsBinding =
|
||||
BottomSheetAttachmentsBinding.inflate(getLayoutInflater());
|
||||
|
||||
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(ctx);
|
||||
|
||||
bottomSheetAttachmentsBinding.addAttachment.setOnClickListener(
|
||||
v1 -> openFileAttachmentActivity());
|
||||
|
||||
bottomSheetAttachmentsBinding.recyclerViewAttachments.setHasFixedSize(true);
|
||||
bottomSheetAttachmentsBinding.recyclerViewAttachments.setLayoutManager(
|
||||
new LinearLayoutManager(ctx));
|
||||
bottomSheetAttachmentsBinding.recyclerViewAttachments.setAdapter(attachmentsAdapter);
|
||||
|
||||
bottomSheetDialog.setContentView(bottomSheetAttachmentsBinding.getRoot());
|
||||
bottomSheetDialog.show();
|
||||
} else {
|
||||
attachmentsAdapter.clearAdapter();
|
||||
openFileAttachmentActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private void openFileAttachmentActivity() {
|
||||
|
||||
Intent data = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
data.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
data.setType("*/*");
|
||||
Intent intent = Intent.createChooser(data, "Choose a file");
|
||||
startActivityForResult.launch(intent);
|
||||
}
|
||||
|
||||
private void processAttachments() {
|
||||
|
||||
for (int i = 0; i < contentUri.size(); i++) {
|
||||
|
||||
File file = AttachmentUtils.getFile(ctx, contentUri.get(i));
|
||||
|
||||
RequestBody requestFile =
|
||||
RequestBody.create(
|
||||
file,
|
||||
MediaType.parse(
|
||||
Objects.requireNonNull(
|
||||
getContentResolver().getType(contentUri.get(i)))));
|
||||
|
||||
uploadAttachments(requestFile, file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadAttachments(RequestBody requestFile, String filename1) {
|
||||
|
||||
Call<Attachment> call3 =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueCreateIssueAttachment(
|
||||
requestFile,
|
||||
issue.getRepository().getOwner(),
|
||||
issue.getRepository().getName(),
|
||||
(long) issue.getIssueIndex(),
|
||||
filename1);
|
||||
|
||||
call3.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<Attachment> call,
|
||||
@NonNull retrofit2.Response<Attachment> response2) {
|
||||
|
||||
if (response2.code() == 201) {
|
||||
new Handler().postDelayed(() -> finish(), 3000);
|
||||
} else if (response2.code() == 401) {
|
||||
|
||||
AlertDialogs.authorizationTokenRevokedDialog(ctx);
|
||||
} else {
|
||||
|
||||
create.setVisible(true);
|
||||
Toasty.show(ctx, getString(R.string.attachmentsSaveError));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) {
|
||||
|
||||
create.setVisible(true);
|
||||
Toasty.show(ctx, getString(R.string.genericServerResponseError));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processEditIssue() {
|
||||
|
||||
String editIssueTitleForm =
|
||||
Objects.requireNonNull(binding.editIssueTitle.getText()).toString();
|
||||
String editIssueDescriptionForm =
|
||||
Objects.requireNonNull(binding.editIssueDescription.getText()).toString();
|
||||
String dueDate = Objects.requireNonNull(binding.editIssueDueDate.getText()).toString();
|
||||
|
||||
if (editIssueTitleForm.isEmpty()) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.issueTitleEmpty));
|
||||
return;
|
||||
}
|
||||
|
||||
editIssue(
|
||||
issue.getRepository().getOwner(),
|
||||
issue.getRepository().getName(),
|
||||
issue.getIssueIndex(),
|
||||
editIssueTitleForm,
|
||||
editIssueDescriptionForm,
|
||||
milestoneId,
|
||||
dueDate);
|
||||
}
|
||||
|
||||
private void editIssue(
|
||||
String repoOwner,
|
||||
String repoName,
|
||||
int issueIndex,
|
||||
String title,
|
||||
String description,
|
||||
int milestoneId,
|
||||
String dueDate) {
|
||||
|
||||
EditIssueOption issueData = new EditIssueOption();
|
||||
issueData.setTitle(title);
|
||||
issueData.setBody(description);
|
||||
String[] date = dueDate.split("-");
|
||||
if (!dueDate.equalsIgnoreCase("")) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.YEAR, Integer.parseInt(date[0]));
|
||||
calendar.set(Calendar.MONTH, Integer.parseInt(date[1]));
|
||||
calendar.set(Calendar.DATE, Integer.parseInt(date[2]));
|
||||
Date dueDate_ = calendar.getTime();
|
||||
issueData.setDueDate(dueDate_);
|
||||
}
|
||||
issueData.setMilestone((long) milestoneId);
|
||||
|
||||
Call<Issue> call =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueEditIssue(repoOwner, repoName, (long) issueIndex, issueData);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<Issue> call,
|
||||
@NonNull retrofit2.Response<Issue> response) {
|
||||
|
||||
if (response.code() == 201) {
|
||||
|
||||
if (issue.getIssueType().equalsIgnoreCase("Pull")) {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.editPrSuccessMessage));
|
||||
} else {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.editIssueSuccessMessage));
|
||||
}
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra("issueEdited", true);
|
||||
// PullRequestsFragment.resumePullRequests =
|
||||
// issue.getIssue().getPullRequest() != null;
|
||||
setResult(200, result);
|
||||
new Handler().postDelayed(() -> finish(), 3000);
|
||||
} else if (response.code() == 401) {
|
||||
|
||||
AlertDialogs.authorizationTokenRevokedDialog(ctx);
|
||||
} else {
|
||||
|
||||
create.setVisible(true);
|
||||
Toasty.show(ctx, getString(R.string.genericError));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Issue> call, @NonNull Throwable t) {
|
||||
create.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showDatePickerDialog() {
|
||||
|
||||
MaterialDatePicker.Builder<Long> builder = MaterialDatePicker.Builder.datePicker();
|
||||
builder.setSelection(Calendar.getInstance().getTimeInMillis());
|
||||
builder.setTitleText(R.string.newIssueDueDateTitle);
|
||||
MaterialDatePicker<Long> materialDatePicker = builder.build();
|
||||
|
||||
String[] locale_ =
|
||||
AppDatabaseSettings.getSettingsValue(ctx, AppDatabaseSettings.APP_LOCALE_KEY)
|
||||
.split("\\|");
|
||||
|
||||
binding.editIssueDueDate.setOnClickListener(
|
||||
v -> materialDatePicker.show(getSupportFragmentManager(), "DATE_PICKER"));
|
||||
|
||||
materialDatePicker.addOnPositiveButtonClickListener(
|
||||
selection -> {
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
calendar.setTimeInMillis(selection);
|
||||
SimpleDateFormat format =
|
||||
new SimpleDateFormat("yyyy-MM-dd", new Locale(locale_[1]));
|
||||
String formattedDate = format.format(calendar.getTime());
|
||||
binding.editIssueDueDate.setText(formattedDate);
|
||||
});
|
||||
}
|
||||
|
||||
private void getIssue(
|
||||
final String repoOwner, final String repoName, int issueIndex, int resultLimit) {
|
||||
|
||||
Call<Issue> call =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueGetIssue(repoOwner, repoName, (long) issueIndex);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<Issue> call,
|
||||
@NonNull retrofit2.Response<Issue> response) {
|
||||
|
||||
if (response.code() == 200) {
|
||||
|
||||
assert response.body() != null;
|
||||
binding.editIssueTitle.setText(response.body().getTitle());
|
||||
binding.editIssueDescription.setText(response.body().getBody());
|
||||
|
||||
Milestone currentMilestone = response.body().getMilestone();
|
||||
|
||||
// get milestones list
|
||||
if (response.body().getId() > 0) {
|
||||
|
||||
Call<List<Milestone>> call_ =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueGetMilestonesList(
|
||||
repoOwner,
|
||||
repoName,
|
||||
msState,
|
||||
null,
|
||||
1,
|
||||
resultLimit);
|
||||
|
||||
call_.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<List<Milestone>> call,
|
||||
@NonNull retrofit2.Response<List<Milestone>>
|
||||
response_) {
|
||||
|
||||
if (response_.code() == 200) {
|
||||
|
||||
List<Milestone> milestonesList_ =
|
||||
response_.body();
|
||||
|
||||
assert milestonesList_ != null;
|
||||
|
||||
Milestone ms = new Milestone();
|
||||
ms.setId(0L);
|
||||
ms.setTitle(
|
||||
getString(
|
||||
R.string
|
||||
.issueCreatedNoMilestone));
|
||||
milestonesList.put(ms.getTitle(), ms);
|
||||
|
||||
if (!milestonesList_.isEmpty()) {
|
||||
|
||||
for (Milestone milestone :
|
||||
milestonesList_) {
|
||||
|
||||
// Don't translate "open" is a enum
|
||||
if (milestone
|
||||
.getState()
|
||||
.equals("open")) {
|
||||
milestonesList.put(
|
||||
milestone.getTitle(),
|
||||
milestone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<>(
|
||||
EditIssueActivity.this,
|
||||
R.layout.list_spinner_items,
|
||||
new ArrayList<>(
|
||||
milestonesList
|
||||
.keySet()));
|
||||
|
||||
binding.editIssueMilestoneSpinner.setAdapter(
|
||||
adapter);
|
||||
|
||||
binding.editIssueMilestoneSpinner
|
||||
.setOnItemClickListener(
|
||||
(parent,
|
||||
view,
|
||||
position,
|
||||
id) -> {
|
||||
if (position == 0) {
|
||||
milestoneId = 0;
|
||||
} else if (view
|
||||
instanceof
|
||||
TextView) {
|
||||
milestoneId =
|
||||
Math.toIntExact(
|
||||
Objects
|
||||
.requireNonNull(
|
||||
milestonesList
|
||||
.get(
|
||||
((TextView)
|
||||
view)
|
||||
.getText()
|
||||
.toString()))
|
||||
.getId());
|
||||
}
|
||||
});
|
||||
|
||||
new Handler(Looper.getMainLooper())
|
||||
.postDelayed(
|
||||
() -> {
|
||||
if (currentMilestone
|
||||
!= null) {
|
||||
milestoneId =
|
||||
Math.toIntExact(
|
||||
currentMilestone
|
||||
.getId());
|
||||
binding
|
||||
.editIssueMilestoneSpinner
|
||||
.setText(
|
||||
currentMilestone
|
||||
.getTitle(),
|
||||
false);
|
||||
} else {
|
||||
milestoneId = 0;
|
||||
binding
|
||||
.editIssueMilestoneSpinner
|
||||
.setText(
|
||||
getString(
|
||||
R
|
||||
.string
|
||||
.issueCreatedNoMilestone),
|
||||
false);
|
||||
}
|
||||
},
|
||||
500);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(
|
||||
@NonNull Call<List<Milestone>> call,
|
||||
@NonNull Throwable t) {
|
||||
|
||||
Log.e("onFailure", t.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
// get milestones list
|
||||
|
||||
if (response.body().getDueDate() != null) {
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
DateFormat formatter = new SimpleDateFormat("yyyy-M-dd");
|
||||
String dueDate = formatter.format(response.body().getDueDate());
|
||||
binding.editIssueDueDate.setText(dueDate);
|
||||
}
|
||||
|
||||
} else if (response.code() == 401) {
|
||||
|
||||
AlertDialogs.authorizationTokenRevokedDialog(ctx);
|
||||
} else {
|
||||
|
||||
Toasty.show(ctx, getString(R.string.genericError));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Issue> call, @NonNull Throwable t) {
|
||||
|
||||
// Log.e("onFailure", t.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void getAttachments() {
|
||||
|
||||
Call<List<Attachment>> call =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.issueListIssueAttachments(
|
||||
issue.getRepository().getOwner(),
|
||||
issue.getRepository().getName(),
|
||||
(long) issue.getIssueIndex());
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<List<Attachment>> call,
|
||||
@NonNull retrofit2.Response<List<Attachment>> response) {
|
||||
|
||||
List<Attachment> attachment = response.body();
|
||||
|
||||
if (response.code() == 200) {
|
||||
assert attachment != null;
|
||||
|
||||
if (!attachment.isEmpty()) {
|
||||
|
||||
binding.attachmentFrame.setVisibility(View.VISIBLE);
|
||||
LinearLayout.LayoutParams paramsAttachment =
|
||||
new LinearLayout.LayoutParams(96, 96);
|
||||
paramsAttachment.setMargins(0, 0, 48, 0);
|
||||
|
||||
for (int i = 0; i < attachment.size(); i++) {
|
||||
|
||||
ImageView attachmentView = new ImageView(ctx);
|
||||
MaterialCardView materialCardView = new MaterialCardView(ctx);
|
||||
materialCardView.setLayoutParams(paramsAttachment);
|
||||
materialCardView.setStrokeWidth(0);
|
||||
materialCardView.setRadius(28);
|
||||
materialCardView.setCardBackgroundColor(Color.TRANSPARENT);
|
||||
|
||||
if (Arrays.asList(
|
||||
"bmp", "gif", "jpg", "jpeg", "png", "webp",
|
||||
"heic", "heif")
|
||||
.contains(
|
||||
FilenameUtils.getExtension(
|
||||
attachment.get(i).getName())
|
||||
.toLowerCase())) {
|
||||
|
||||
Glide.with(ctx)
|
||||
.load(
|
||||
attachment.get(i).getBrowserDownloadUrl()
|
||||
+ "?token="
|
||||
+ token)
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.placeholder(R.drawable.loader_animated)
|
||||
.centerCrop()
|
||||
.error(R.drawable.ic_close)
|
||||
.into(attachmentView);
|
||||
|
||||
binding.attachmentsView.addView(materialCardView);
|
||||
attachmentView.setLayoutParams(paramsAttachment);
|
||||
materialCardView.addView(attachmentView);
|
||||
|
||||
int finalI1 = i;
|
||||
materialCardView.setOnClickListener(
|
||||
v1 ->
|
||||
imageViewDialog(
|
||||
attachment
|
||||
.get(finalI1)
|
||||
.getBrowserDownloadUrl()));
|
||||
|
||||
} else {
|
||||
|
||||
attachmentView.setImageResource(
|
||||
R.drawable.ic_file_download);
|
||||
attachmentView.setPadding(4, 4, 4, 4);
|
||||
binding.attachmentsView.addView(materialCardView);
|
||||
attachmentView.setLayoutParams(paramsAttachment);
|
||||
materialCardView.addView(attachmentView);
|
||||
|
||||
int finalI = i;
|
||||
materialCardView.setOnClickListener(
|
||||
v1 -> {
|
||||
filesize = attachment.get(finalI).getSize();
|
||||
filename = attachment.get(finalI).getName();
|
||||
filehash = attachment.get(finalI).getUuid();
|
||||
requestFileDownload();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.attachmentFrame.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(
|
||||
@NonNull Call<List<Attachment>> call, @NonNull Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void requestFileDownload() {
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.putExtra(Intent.EXTRA_TITLE, filename);
|
||||
intent.setType("*/*");
|
||||
|
||||
downloadAttachmentLauncher.launch(intent);
|
||||
}
|
||||
|
||||
private void imageViewDialog(String url) {
|
||||
|
||||
CustomImageViewDialogBinding imageViewDialogBinding =
|
||||
CustomImageViewDialogBinding.inflate(LayoutInflater.from(ctx));
|
||||
View view = imageViewDialogBinding.getRoot();
|
||||
materialAlertDialogBuilder.setView(view);
|
||||
|
||||
materialAlertDialogBuilder.setNeutralButton(getString(R.string.close), null);
|
||||
|
||||
Glide.with(ctx)
|
||||
.asBitmap()
|
||||
.load(url + "?token=" + token)
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.placeholder(R.drawable.loader_animated)
|
||||
.centerCrop()
|
||||
.error(R.drawable.ic_close)
|
||||
.into(
|
||||
new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(
|
||||
@NonNull Bitmap resource,
|
||||
Transition<? super Bitmap> transition) {
|
||||
imageViewDialogBinding.imageView.setImageBitmap(resource);
|
||||
imageViewDialogBinding.imageView.buildDrawingCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {}
|
||||
});
|
||||
|
||||
materialAlertDialogBuilder.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
issue.getRepository().checkAccountSwitch(this);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.databinding.ActivityRepoDetailBinding;
|
||||
import org.mian.gitnex.fragments.BottomSheetCreateIssue;
|
||||
import org.mian.gitnex.fragments.BottomSheetCreateMilestone;
|
||||
import org.mian.gitnex.fragments.BottomSheetCreatePullRequest;
|
||||
import org.mian.gitnex.fragments.BottomSheetCreateRelease;
|
||||
import org.mian.gitnex.fragments.BottomSheetCreateWiki;
|
||||
import org.mian.gitnex.fragments.BottomsheetRepoMenu;
|
||||
@@ -618,7 +619,8 @@ public class RepoDetailActivity extends BaseActivity
|
||||
|
||||
case "pullNew":
|
||||
switchTab("prs", R.id.btn_nav_prs);
|
||||
startActivity(repository.getIntent(this, CreatePullRequestActivity.class));
|
||||
BottomSheetCreatePullRequest.newInstance(repository, null)
|
||||
.show(getSupportFragmentManager(), "CREATE_PULL_REQUEST");
|
||||
break;
|
||||
|
||||
case "releases":
|
||||
|
||||
@@ -5,10 +5,12 @@ import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.gitnex.tea4j.v2.models.User;
|
||||
@@ -25,16 +27,30 @@ public class AssigneeSelectionAdapter
|
||||
private final List<User> assignees;
|
||||
private final Set<String> selectedAssignees;
|
||||
private final RequestOptions avatarOptions;
|
||||
private final String excludeUser;
|
||||
private final List<User> filteredAssignees = new ArrayList<>();
|
||||
|
||||
public AssigneeSelectionAdapter(List<User> assignees, Set<String> selectedAssignees) {
|
||||
public AssigneeSelectionAdapter(
|
||||
List<User> assignees, Set<String> selectedAssignees, @Nullable String excludeUser) {
|
||||
this.assignees = assignees;
|
||||
this.selectedAssignees = selectedAssignees;
|
||||
this.excludeUser = excludeUser;
|
||||
this.avatarOptions =
|
||||
new RequestOptions()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.placeholder(R.drawable.loader_animated)
|
||||
.error(R.drawable.ic_person)
|
||||
.centerCrop();
|
||||
filterAssignees();
|
||||
}
|
||||
|
||||
private void filterAssignees() {
|
||||
filteredAssignees.clear();
|
||||
for (User user : assignees) {
|
||||
if (excludeUser == null || !excludeUser.equals(user.getLogin())) {
|
||||
filteredAssignees.add(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
@@ -48,7 +64,7 @@ public class AssigneeSelectionAdapter
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
User user = assignees.get(position);
|
||||
User user = filteredAssignees.get(position);
|
||||
Context context = holder.itemView.getContext();
|
||||
|
||||
String fullName = user.getFullName();
|
||||
@@ -87,13 +103,18 @@ public class AssigneeSelectionAdapter
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return assignees != null ? assignees.size() : 0;
|
||||
return filteredAssignees.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return filteredAssignees.isEmpty();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void updateList(List<User> newList) {
|
||||
this.assignees.clear();
|
||||
this.assignees.addAll(newList);
|
||||
filterAssignees();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ import android.content.Intent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.vdurmont.emoji.EmojiParser;
|
||||
@@ -19,9 +17,7 @@ import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.activities.BaseActivity;
|
||||
import org.mian.gitnex.activities.CreateNoteActivity;
|
||||
import org.mian.gitnex.activities.CreatePullRequestActivity;
|
||||
import org.mian.gitnex.database.api.BaseApi;
|
||||
import org.mian.gitnex.database.api.NotesApi;
|
||||
import org.mian.gitnex.database.models.Notes;
|
||||
@@ -71,8 +67,6 @@ public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NotesViewHol
|
||||
if ("insert".equalsIgnoreCase(insert)) {
|
||||
if (itemClickListener != null) {
|
||||
itemClickListener.onItemClick(note);
|
||||
} else {
|
||||
performInsert();
|
||||
}
|
||||
} else {
|
||||
Intent intent = new Intent(ctx, CreateNoteActivity.class);
|
||||
@@ -99,27 +93,6 @@ public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NotesViewHol
|
||||
});
|
||||
}
|
||||
|
||||
private void performInsert() {
|
||||
|
||||
if (!(ctx instanceof BaseActivity activity)) return;
|
||||
|
||||
EditText targetField = null;
|
||||
AlertDialog dialogToDismiss = null;
|
||||
|
||||
if (activity instanceof CreatePullRequestActivity prAct) {
|
||||
targetField = prAct.findViewById(R.id.prBody);
|
||||
dialogToDismiss = prAct.dialogNotes;
|
||||
}
|
||||
|
||||
if (targetField != null) {
|
||||
targetField.append(note.getContent());
|
||||
|
||||
if (dialogToDismiss != null && dialogToDismiss.isShowing()) {
|
||||
dialogToDismiss.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void bind(Notes note) {
|
||||
this.note = note;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.mian.gitnex.activities.IssueDetailActivity;
|
||||
import org.mian.gitnex.activities.ProfileActivity;
|
||||
import org.mian.gitnex.activities.RepoDetailActivity;
|
||||
import org.mian.gitnex.databinding.ListPrBinding;
|
||||
import org.mian.gitnex.fragments.BottomSheetCreatePullRequest;
|
||||
import org.mian.gitnex.helpers.AppDatabaseSettings;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.AvatarGenerator;
|
||||
@@ -31,6 +32,7 @@ import org.mian.gitnex.helpers.Markdown;
|
||||
import org.mian.gitnex.helpers.TimeHelper;
|
||||
import org.mian.gitnex.helpers.Toasty;
|
||||
import org.mian.gitnex.helpers.contexts.IssueContext;
|
||||
import org.mian.gitnex.helpers.contexts.RepositoryContext;
|
||||
|
||||
/**
|
||||
* @author mmarif
|
||||
@@ -146,6 +148,20 @@ public class PullRequestsAdapter
|
||||
binding.mergedBadge.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// TEMPORARY: remove later once PR edit is implemented
|
||||
binding.prNumber.setOnClickListener(
|
||||
v -> {
|
||||
if (context instanceof RepoDetailActivity) {
|
||||
RepositoryContext repository =
|
||||
((RepoDetailActivity) context).repository;
|
||||
BottomSheetCreatePullRequest.newInstance(repository, pr)
|
||||
.show(
|
||||
((RepoDetailActivity) context)
|
||||
.getSupportFragmentManager(),
|
||||
"EDIT_PULL_REQUEST");
|
||||
}
|
||||
});
|
||||
|
||||
binding.userName.setText(pr.getUser().getLogin());
|
||||
binding.repoFullName.setText(pr.getBase().getRepo().getFullName());
|
||||
binding.prNumber.setText(context.getString(R.string.hash_with_text, pr.getNumber()));
|
||||
|
||||
@@ -44,7 +44,6 @@ public class BottomSheetAddCollaborator extends BottomSheetDialogFragment
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NORMAL, R.style.Custom_BottomSheet);
|
||||
if (getArguments() != null) {
|
||||
repository = RepositoryContext.fromBundle(getArguments());
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ public class BottomSheetAddTeamMember extends BottomSheetDialogFragment {
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NORMAL, R.style.Custom_BottomSheet);
|
||||
}
|
||||
|
||||
@Nullable @Override
|
||||
@@ -74,6 +73,8 @@ public class BottomSheetAddTeamMember extends BottomSheetDialogFragment {
|
||||
viewModel.loadCurrentMembers(requireContext(), teamId);
|
||||
}
|
||||
|
||||
binding.sheetTitle.setText(R.string.add_team_member);
|
||||
|
||||
setupRecyclerView();
|
||||
setupListeners();
|
||||
observeViewModel();
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import java.util.ArrayList;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.adapters.OrganizationTeamRepositoriesAdapter;
|
||||
import org.mian.gitnex.databinding.BottomsheetAddTeamRepoBinding;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
@@ -42,7 +41,6 @@ public class BottomSheetAddTeamRepo extends BottomSheetDialogFragment {
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NORMAL, R.style.Custom_BottomSheet);
|
||||
if (getArguments() != null) {
|
||||
teamId = getArguments().getLong("teamId");
|
||||
orgName = getArguments().getString("orgName");
|
||||
|
||||
@@ -42,16 +42,25 @@ public class BottomSheetAssigneesPicker extends BottomSheetDialogFragment {
|
||||
private AssigneesViewModel assigneesViewModel;
|
||||
private AssigneeSelectionAdapter adapter;
|
||||
private int resultLimit;
|
||||
private String excludeUser = null;
|
||||
|
||||
public static BottomSheetAssigneesPicker newInstance(
|
||||
RepositoryContext repo, List<String> current) {
|
||||
RepositoryContext repo, List<String> current, @Nullable String excludeUser) {
|
||||
BottomSheetAssigneesPicker f = new BottomSheetAssigneesPicker();
|
||||
Bundle b = repo.getBundle();
|
||||
b.putStringArrayList("current_assignees", new ArrayList<>(current));
|
||||
if (excludeUser != null) {
|
||||
b.putString("exclude_user", excludeUser);
|
||||
}
|
||||
f.setArguments(b);
|
||||
return f;
|
||||
}
|
||||
|
||||
public static BottomSheetAssigneesPicker newInstance(
|
||||
RepositoryContext repo, List<String> current) {
|
||||
return newInstance(repo, current, null);
|
||||
}
|
||||
|
||||
public void setOnAssigneesSelectedListener(OnAssigneesSelectedListener l) {
|
||||
this.listener = l;
|
||||
}
|
||||
@@ -68,6 +77,7 @@ public class BottomSheetAssigneesPicker extends BottomSheetDialogFragment {
|
||||
repository = RepositoryContext.fromBundle(args);
|
||||
selectedAssignees =
|
||||
new HashSet<>(Objects.requireNonNull(args.getStringArrayList("current_assignees")));
|
||||
excludeUser = args.getString("exclude_user");
|
||||
resultLimit = Constants.getCurrentResultLimit(requireContext());
|
||||
|
||||
setupRecyclerView();
|
||||
@@ -101,7 +111,7 @@ public class BottomSheetAssigneesPicker extends BottomSheetDialogFragment {
|
||||
private void setupRecyclerView() {
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
|
||||
binding.rvAssignees.setLayoutManager(layoutManager);
|
||||
adapter = new AssigneeSelectionAdapter(new ArrayList<>(), selectedAssignees);
|
||||
adapter = new AssigneeSelectionAdapter(new ArrayList<>(), selectedAssignees, excludeUser);
|
||||
binding.rvAssignees.setAdapter(adapter);
|
||||
|
||||
EndlessRecyclerViewScrollListener scrollListener =
|
||||
@@ -122,9 +132,12 @@ public class BottomSheetAssigneesPicker extends BottomSheetDialogFragment {
|
||||
list -> {
|
||||
List<User> data = (list != null) ? list : new ArrayList<>();
|
||||
adapter.updateList(data);
|
||||
if (!data.isEmpty()) {
|
||||
if (!adapter.isEmpty()) {
|
||||
binding.rvAssignees.setVisibility(View.VISIBLE);
|
||||
binding.layoutEmpty.getRoot().setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.rvAssignees.setVisibility(View.GONE);
|
||||
binding.layoutEmpty.getRoot().setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,960 @@
|
||||
package org.mian.gitnex.fragments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import org.gitnex.tea4j.v2.models.CreatePullRequestOption;
|
||||
import org.gitnex.tea4j.v2.models.EditPullRequestOption;
|
||||
import org.gitnex.tea4j.v2.models.Label;
|
||||
import org.gitnex.tea4j.v2.models.PullRequest;
|
||||
import org.gitnex.tea4j.v2.models.User;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.adapters.CreateAttachmentsAdapter;
|
||||
import org.mian.gitnex.database.api.BaseApi;
|
||||
import org.mian.gitnex.database.api.UserAccountsApi;
|
||||
import org.mian.gitnex.database.models.UserAccount;
|
||||
import org.mian.gitnex.databinding.BottomsheetCreatePullRequestBinding;
|
||||
import org.mian.gitnex.helpers.AlertDialogs;
|
||||
import org.mian.gitnex.helpers.AppDatabaseSettings;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.TinyDB;
|
||||
import org.mian.gitnex.helpers.Toasty;
|
||||
import org.mian.gitnex.helpers.attachments.AttachmentManager;
|
||||
import org.mian.gitnex.helpers.contexts.RepositoryContext;
|
||||
import org.mian.gitnex.viewmodels.AttachmentsViewModel;
|
||||
import org.mian.gitnex.viewmodels.PullRequestsViewModel;
|
||||
|
||||
/**
|
||||
* @author mmarif
|
||||
*/
|
||||
public class BottomSheetCreatePullRequest extends BottomSheetDialogFragment {
|
||||
|
||||
private BottomsheetCreatePullRequestBinding binding;
|
||||
private PullRequestsViewModel viewModel;
|
||||
private RepositoryContext repoContext;
|
||||
private PullRequest prToEdit;
|
||||
private String selectedMergeInto = null;
|
||||
private String selectedPullFrom = null;
|
||||
private Set<String> selectedLabels = new HashSet<>();
|
||||
private final List<Long> selectedLabelIds = new ArrayList<>();
|
||||
private String selectedMilestone = null;
|
||||
private Long selectedMilestoneId = null;
|
||||
private Set<String> selectedAssignees = new HashSet<>();
|
||||
private Set<String> selectedReviewers = new HashSet<>();
|
||||
private String selectedDueDate = null;
|
||||
private int maxAttachmentSize = -1;
|
||||
private int maxNumberOfAttachments = -1;
|
||||
private AttachmentManager attachmentManager;
|
||||
private AttachmentsViewModel attachmentsViewModel;
|
||||
protected TinyDB tinyDB;
|
||||
|
||||
public static BottomSheetCreatePullRequest newInstance(
|
||||
RepositoryContext repository, @Nullable PullRequest pr) {
|
||||
BottomSheetCreatePullRequest fragment = new BottomSheetCreatePullRequest();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable("repo_context", repository);
|
||||
if (pr != null) {
|
||||
args.putSerializable("pr_item", pr);
|
||||
}
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getArguments() != null) {
|
||||
repoContext = (RepositoryContext) getArguments().getSerializable("repo_context");
|
||||
prToEdit = (PullRequest) getArguments().getSerializable("pr_item");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable @Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
binding = BottomsheetCreatePullRequestBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
viewModel = new ViewModelProvider(requireActivity()).get(PullRequestsViewModel.class);
|
||||
attachmentsViewModel =
|
||||
new ViewModelProvider(requireActivity()).get(AttachmentsViewModel.class);
|
||||
|
||||
this.tinyDB = TinyDB.getInstance(requireContext());
|
||||
|
||||
viewModel.clearCreatedPr();
|
||||
viewModel.clearUpdatedPr();
|
||||
attachmentsViewModel.reset();
|
||||
|
||||
setupUI();
|
||||
setupListeners();
|
||||
setupAttachments();
|
||||
observeViewModel();
|
||||
observeAttachmentsViewModel();
|
||||
}
|
||||
|
||||
private void setupUI() {
|
||||
boolean hasWriteAccess =
|
||||
repoContext.getPermissions() != null && repoContext.getPermissions().isPush();
|
||||
|
||||
binding.cardMergeInto.cardIcon.setImageResource(R.drawable.ic_branch);
|
||||
binding.cardMergeInto.tvCardLabel.setText(R.string.mergeIntoBranch);
|
||||
binding.cardMergeInto.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
binding.cardPullFrom.cardIcon.setImageResource(R.drawable.ic_branch);
|
||||
binding.cardPullFrom.tvCardLabel.setText(R.string.pullFromBranch);
|
||||
binding.cardPullFrom.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
binding.cardMilestone.cardIcon.setImageResource(R.drawable.ic_milestone);
|
||||
binding.cardMilestone.tvCardLabel.setText(R.string.milestone);
|
||||
binding.cardMilestone.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
binding.cardLabels.cardIcon.setImageResource(R.drawable.ic_label);
|
||||
binding.cardLabels.tvCardLabel.setText(R.string.newIssueLabelsTitle);
|
||||
binding.cardLabels.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
binding.cardAssignees.cardIcon.setImageResource(R.drawable.ic_person);
|
||||
binding.cardAssignees.tvCardLabel.setText(R.string.newIssueAssigneesListTitle);
|
||||
binding.cardAssignees.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
binding.cardReviewers.cardIcon.setImageResource(R.drawable.ic_followers);
|
||||
binding.cardReviewers.tvCardLabel.setText(R.string.reviewers);
|
||||
binding.cardReviewers.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
binding.cardDueDate.cardIcon.setImageResource(R.drawable.ic_calendar);
|
||||
binding.cardDueDate.tvCardLabel.setText(R.string.newIssueDueDateTitle);
|
||||
binding.cardDueDate.getRoot().setVisibility(hasWriteAccess ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (prToEdit != null) {
|
||||
binding.sheetTitle.setText(R.string.edit_pr);
|
||||
binding.prTitle.setText(prToEdit.getTitle());
|
||||
binding.prBody.setText(prToEdit.getBody());
|
||||
binding.btnSubmit.setText(R.string.update);
|
||||
|
||||
binding.cardMergeInto.getRoot().setVisibility(View.GONE);
|
||||
binding.cardPullFrom.getRoot().setVisibility(View.GONE);
|
||||
binding.cardReviewers.getRoot().setVisibility(View.GONE);
|
||||
|
||||
binding.switchAllowMaintainerEdit.setVisibility(View.VISIBLE);
|
||||
|
||||
if (hasWriteAccess) {
|
||||
if (prToEdit.getLabels() != null && !prToEdit.getLabels().isEmpty()) {
|
||||
for (Label label : prToEdit.getLabels()) {
|
||||
selectedLabels.add(label.getName());
|
||||
if (label.getId() != null) {
|
||||
selectedLabelIds.add(label.getId());
|
||||
}
|
||||
}
|
||||
updateLabelsDisplay();
|
||||
}
|
||||
|
||||
if (prToEdit.getMilestone() != null) {
|
||||
selectedMilestone = prToEdit.getMilestone().getTitle();
|
||||
selectedMilestoneId = prToEdit.getMilestone().getId();
|
||||
updateMilestoneDisplay();
|
||||
}
|
||||
|
||||
if (prToEdit.getAssignees() != null && !prToEdit.getAssignees().isEmpty()) {
|
||||
for (User assignee : prToEdit.getAssignees()) {
|
||||
selectedAssignees.add(assignee.getLogin());
|
||||
}
|
||||
updateAssigneesDisplay();
|
||||
}
|
||||
|
||||
if (prToEdit.getDueDate() != null) {
|
||||
selectedDueDate = formatDateForDisplay(prToEdit.getDueDate());
|
||||
updateDueDateDisplay();
|
||||
}
|
||||
|
||||
if (prToEdit.getBase() != null && prToEdit.getBase().getRef() != null) {
|
||||
selectedMergeInto = prToEdit.getBase().getRef();
|
||||
updateMergeIntoDisplay();
|
||||
}
|
||||
if (prToEdit.getHead() != null && prToEdit.getHead().getRef() != null) {
|
||||
selectedPullFrom = prToEdit.getHead().getRef();
|
||||
updatePullFromDisplay();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.sheetTitle.setText(R.string.create_pr);
|
||||
binding.btnSubmit.setText(R.string.create_pr);
|
||||
binding.switchAllowMaintainerEdit.setVisibility(View.GONE);
|
||||
|
||||
if (hasWriteAccess) {
|
||||
updateLabelsDisplay();
|
||||
updateMilestoneDisplay();
|
||||
updateAssigneesDisplay();
|
||||
updateReviewersDisplay();
|
||||
updateDueDateDisplay();
|
||||
updateMergeIntoDisplay();
|
||||
updatePullFromDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWriteAccess) {
|
||||
updateClearButtonVisibility();
|
||||
updateMilestoneClearButtonVisibility();
|
||||
updateAssigneesClearButtonVisibility();
|
||||
updateReviewersClearButtonVisibility();
|
||||
updateDueDateClearButtonVisibility();
|
||||
updateMergeIntoClearButtonVisibility();
|
||||
updatePullFromClearButtonVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
binding.btnClose.setOnClickListener(v -> dismiss());
|
||||
binding.btnExpand.setOnClickListener(v -> openFullScreenEditor());
|
||||
binding.btnSubmit.setOnClickListener(v -> submitAction());
|
||||
|
||||
boolean hasWriteAccess =
|
||||
repoContext.getPermissions() != null && repoContext.getPermissions().isPush();
|
||||
|
||||
binding.cardMergeInto.getRoot().setOnClickListener(v -> openBranchPicker("merge"));
|
||||
binding.cardMergeInto.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedMergeInto = null;
|
||||
updateMergeIntoDisplay();
|
||||
updateMergeIntoClearButtonVisibility();
|
||||
});
|
||||
|
||||
binding.cardPullFrom.getRoot().setOnClickListener(v -> openBranchPicker("pull"));
|
||||
binding.cardPullFrom.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedPullFrom = null;
|
||||
updatePullFromDisplay();
|
||||
updatePullFromClearButtonVisibility();
|
||||
});
|
||||
|
||||
if (hasWriteAccess) {
|
||||
binding.cardMilestone.getRoot().setOnClickListener(v -> openMilestonePicker());
|
||||
binding.cardMilestone.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedMilestone = null;
|
||||
selectedMilestoneId = null;
|
||||
updateMilestoneDisplay();
|
||||
updateMilestoneClearButtonVisibility();
|
||||
});
|
||||
|
||||
binding.cardLabels.getRoot().setOnClickListener(v -> openLabelPicker());
|
||||
binding.cardLabels.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedLabels.clear();
|
||||
selectedLabelIds.clear();
|
||||
updateLabelsDisplay();
|
||||
updateClearButtonVisibility();
|
||||
});
|
||||
|
||||
binding.cardAssignees.getRoot().setOnClickListener(v -> openAssigneesPicker());
|
||||
binding.cardAssignees.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedAssignees.clear();
|
||||
updateAssigneesDisplay();
|
||||
updateAssigneesClearButtonVisibility();
|
||||
});
|
||||
|
||||
binding.cardReviewers.getRoot().setOnClickListener(v -> openReviewersPicker());
|
||||
binding.cardReviewers.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedReviewers.clear();
|
||||
updateReviewersDisplay();
|
||||
updateReviewersClearButtonVisibility();
|
||||
});
|
||||
|
||||
binding.cardDueDate.getRoot().setOnClickListener(v -> openDatePicker());
|
||||
binding.cardDueDate.btnClear.setOnClickListener(
|
||||
v -> {
|
||||
selectedDueDate = null;
|
||||
updateDueDateDisplay();
|
||||
updateDueDateClearButtonVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
binding.cardAttachments.btnAddAttachment.setOnClickListener(
|
||||
v -> {
|
||||
if (attachmentManager != null) {
|
||||
attachmentManager.openFilePicker();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openFullScreenEditor() {
|
||||
BottomSheetFullScreenEditor editorBottomSheet =
|
||||
BottomSheetFullScreenEditor.newInstance(
|
||||
Objects.requireNonNull(binding.prBody.getText()).toString(),
|
||||
repoContext,
|
||||
true,
|
||||
true);
|
||||
|
||||
editorBottomSheet.setEditorListener(
|
||||
newContent -> {
|
||||
binding.prBody.setText(newContent);
|
||||
binding.prBody.setSelection(newContent != null ? newContent.length() : 0);
|
||||
});
|
||||
|
||||
editorBottomSheet.show(getParentFragmentManager(), "FULLSCREEN_EDITOR");
|
||||
}
|
||||
|
||||
private void submitAction() {
|
||||
String title =
|
||||
binding.prTitle.getText() != null
|
||||
? binding.prTitle.getText().toString().trim()
|
||||
: "";
|
||||
String body =
|
||||
binding.prBody.getText() != null ? binding.prBody.getText().toString().trim() : "";
|
||||
|
||||
if (title.isEmpty()) {
|
||||
Toasty.show(requireContext(), R.string.titleError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMergeInto == null || selectedMergeInto.isEmpty()) {
|
||||
Toasty.show(requireContext(), R.string.mergeIntoError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedPullFrom == null || selectedPullFrom.isEmpty()) {
|
||||
Toasty.show(requireContext(), R.string.pullFromError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMergeInto.equals(selectedPullFrom)) {
|
||||
Toasty.show(requireContext(), R.string.sameBranchesError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCurrentUserInReviewers()) {
|
||||
Toasty.show(requireContext(), R.string.cannotAddSelfAsReviewer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prToEdit != null) {
|
||||
submitUpdatePr(title, body);
|
||||
} else {
|
||||
submitCreatePr(title, body);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCurrentUserInReviewers() {
|
||||
if (selectedReviewers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserAccountsApi userAccountsApi =
|
||||
BaseApi.getInstance(requireContext(), UserAccountsApi.class);
|
||||
if (userAccountsApi != null) {
|
||||
int currentAccountId = tinyDB.getInt("currentActiveAccountId", -1);
|
||||
UserAccount currentAccount = userAccountsApi.getAccountById(currentAccountId);
|
||||
if (currentAccount != null && currentAccount.getUserName() != null) {
|
||||
return selectedReviewers.contains(currentAccount.getUserName());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void submitCreatePr(String title, String body) {
|
||||
CreatePullRequestOption prData = new CreatePullRequestOption();
|
||||
prData.setTitle(title);
|
||||
prData.setBody(body);
|
||||
prData.setBase(selectedMergeInto);
|
||||
prData.setHead(selectedPullFrom);
|
||||
|
||||
if (selectedMilestoneId != null) {
|
||||
prData.setMilestone(selectedMilestoneId);
|
||||
}
|
||||
|
||||
if (!selectedLabelIds.isEmpty()) {
|
||||
prData.setLabels(new ArrayList<>(selectedLabelIds));
|
||||
}
|
||||
|
||||
if (!selectedAssignees.isEmpty()) {
|
||||
prData.setAssignees(new ArrayList<>(selectedAssignees));
|
||||
}
|
||||
|
||||
if (!selectedReviewers.isEmpty()) {
|
||||
prData.setReviewers(new ArrayList<>(selectedReviewers));
|
||||
}
|
||||
|
||||
Date dueDate = getDueDateForApi();
|
||||
if (dueDate != null) {
|
||||
prData.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
viewModel.createPullRequest(
|
||||
requireContext(), repoContext.getOwner(), repoContext.getName(), prData);
|
||||
}
|
||||
|
||||
private void submitUpdatePr(String title, String body) {
|
||||
EditPullRequestOption prData = new EditPullRequestOption();
|
||||
prData.setTitle(title);
|
||||
prData.setBody(body);
|
||||
prData.setAllowMaintainerEdit(binding.switchAllowMaintainerEdit.isChecked());
|
||||
|
||||
if (selectedMilestoneId != null) {
|
||||
prData.setMilestone(selectedMilestoneId);
|
||||
}
|
||||
|
||||
if (!selectedLabelIds.isEmpty()) {
|
||||
prData.setLabels(new ArrayList<>(selectedLabelIds));
|
||||
}
|
||||
|
||||
if (!selectedAssignees.isEmpty()) {
|
||||
prData.setAssignees(new ArrayList<>(selectedAssignees));
|
||||
}
|
||||
|
||||
Date dueDate = getDueDateForApi();
|
||||
if (dueDate != null) {
|
||||
prData.setDueDate(dueDate);
|
||||
}
|
||||
|
||||
viewModel.updatePullRequest(
|
||||
requireContext(),
|
||||
repoContext.getOwner(),
|
||||
repoContext.getName(),
|
||||
prToEdit.getNumber(),
|
||||
prData);
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel
|
||||
.getIsCreating()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
isCreating -> {
|
||||
if (prToEdit == null) {
|
||||
binding.loadingIndicator.setVisibility(
|
||||
isCreating ? View.VISIBLE : View.GONE);
|
||||
binding.btnSubmit.setEnabled(!isCreating);
|
||||
binding.btnSubmit.setText(
|
||||
isCreating ? "" : getString(R.string.create_pr));
|
||||
}
|
||||
});
|
||||
|
||||
viewModel
|
||||
.getCreatedPr()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
pr -> {
|
||||
if (pr != null) {
|
||||
handlePrSuccess(pr.getNumber());
|
||||
}
|
||||
});
|
||||
|
||||
viewModel
|
||||
.getCreateError()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
handleError(error);
|
||||
viewModel.clearCreateError();
|
||||
}
|
||||
});
|
||||
|
||||
viewModel
|
||||
.getIsUpdating()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
isUpdating -> {
|
||||
if (prToEdit != null) {
|
||||
binding.loadingIndicator.setVisibility(
|
||||
isUpdating ? View.VISIBLE : View.GONE);
|
||||
binding.btnSubmit.setEnabled(!isUpdating);
|
||||
binding.btnSubmit.setText(
|
||||
isUpdating ? "" : getString(R.string.update));
|
||||
}
|
||||
});
|
||||
|
||||
viewModel
|
||||
.getUpdatedPr()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
pr -> {
|
||||
if (pr != null) {
|
||||
handlePrSuccess(pr.getNumber());
|
||||
}
|
||||
});
|
||||
|
||||
viewModel
|
||||
.getUpdateError()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
handleError(error);
|
||||
viewModel.clearUpdateError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleError(String error) {
|
||||
if (error.equals("UNAUTHORIZED")) {
|
||||
AlertDialogs.authorizationTokenRevokedDialog(requireContext());
|
||||
} else {
|
||||
Toasty.show(requireContext(), error);
|
||||
}
|
||||
}
|
||||
|
||||
private void openBranchPicker(String type) {
|
||||
BottomsheetBranchPicker branchPicker =
|
||||
BottomsheetBranchPicker.newInstance(
|
||||
repoContext.getOwner(),
|
||||
repoContext.getName(),
|
||||
type.equals("merge") ? selectedMergeInto : selectedPullFrom);
|
||||
|
||||
branchPicker.setOnBranchSelectedListener(
|
||||
branchName -> {
|
||||
if (type.equals("merge")) {
|
||||
selectedMergeInto = branchName;
|
||||
updateMergeIntoDisplay();
|
||||
updateMergeIntoClearButtonVisibility();
|
||||
} else {
|
||||
selectedPullFrom = branchName;
|
||||
updatePullFromDisplay();
|
||||
updatePullFromClearButtonVisibility();
|
||||
}
|
||||
});
|
||||
|
||||
branchPicker.show(getParentFragmentManager(), "BRANCH_PICKER_" + type.toUpperCase());
|
||||
}
|
||||
|
||||
private void updateMergeIntoDisplay() {
|
||||
if (selectedMergeInto == null || selectedMergeInto.isEmpty()) {
|
||||
binding.cardMergeInto.tvSelectedText.setText(R.string.add_release_branch);
|
||||
} else {
|
||||
binding.cardMergeInto.tvSelectedText.setText(selectedMergeInto);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMergeIntoClearButtonVisibility() {
|
||||
binding.cardMergeInto.btnClear.setVisibility(
|
||||
selectedMergeInto == null || selectedMergeInto.isEmpty()
|
||||
? View.GONE
|
||||
: View.VISIBLE);
|
||||
}
|
||||
|
||||
private void updatePullFromDisplay() {
|
||||
if (selectedPullFrom == null || selectedPullFrom.isEmpty()) {
|
||||
binding.cardPullFrom.tvSelectedText.setText(R.string.add_release_branch);
|
||||
} else {
|
||||
binding.cardPullFrom.tvSelectedText.setText(selectedPullFrom);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePullFromClearButtonVisibility() {
|
||||
binding.cardPullFrom.btnClear.setVisibility(
|
||||
selectedPullFrom == null || selectedPullFrom.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void openLabelPicker() {
|
||||
BottomSheetLabelPicker labelPicker =
|
||||
BottomSheetLabelPicker.newInstance(repoContext, new ArrayList<>(selectedLabels));
|
||||
labelPicker.setOnLabelsSelectedWithIdsListener(
|
||||
(selected, labelIds) -> {
|
||||
selectedLabels = selected;
|
||||
selectedLabelIds.clear();
|
||||
selectedLabelIds.addAll(labelIds.values());
|
||||
updateLabelsDisplay();
|
||||
updateClearButtonVisibility();
|
||||
});
|
||||
labelPicker.show(getParentFragmentManager(), "LABEL_PICKER");
|
||||
}
|
||||
|
||||
private void updateLabelsDisplay() {
|
||||
if (selectedLabels.isEmpty()) {
|
||||
binding.cardLabels.tvSelectedText.setText(R.string.add_labels);
|
||||
} else {
|
||||
binding.cardLabels.tvSelectedText.setText(String.join(", ", selectedLabels));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateClearButtonVisibility() {
|
||||
binding.cardLabels.btnClear.setVisibility(
|
||||
selectedLabels.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void openMilestonePicker() {
|
||||
List<String> current =
|
||||
selectedMilestone != null
|
||||
? Collections.singletonList(selectedMilestone)
|
||||
: new ArrayList<>();
|
||||
BottomSheetMilestonePicker milestonePicker =
|
||||
BottomSheetMilestonePicker.newInstance(repoContext, current);
|
||||
milestonePicker.setOnMilestonesSelectedWithIdsListener(
|
||||
(selected, milestoneIds) -> {
|
||||
if (selected.isEmpty()) {
|
||||
selectedMilestone = null;
|
||||
selectedMilestoneId = null;
|
||||
} else {
|
||||
selectedMilestone = selected.iterator().next();
|
||||
selectedMilestoneId = milestoneIds.get(selectedMilestone);
|
||||
}
|
||||
updateMilestoneDisplay();
|
||||
updateMilestoneClearButtonVisibility();
|
||||
});
|
||||
milestonePicker.show(getParentFragmentManager(), "MILESTONE_PICKER");
|
||||
}
|
||||
|
||||
private void updateMilestoneDisplay() {
|
||||
if (selectedMilestone == null || selectedMilestone.isEmpty()) {
|
||||
binding.cardMilestone.tvSelectedText.setText(R.string.add_milestone);
|
||||
} else {
|
||||
binding.cardMilestone.tvSelectedText.setText(selectedMilestone);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMilestoneClearButtonVisibility() {
|
||||
binding.cardMilestone.btnClear.setVisibility(
|
||||
selectedMilestone == null || selectedMilestone.isEmpty()
|
||||
? View.GONE
|
||||
: View.VISIBLE);
|
||||
}
|
||||
|
||||
private void openAssigneesPicker() {
|
||||
BottomSheetAssigneesPicker assigneesPicker =
|
||||
BottomSheetAssigneesPicker.newInstance(
|
||||
repoContext, new ArrayList<>(selectedAssignees));
|
||||
assigneesPicker.setOnAssigneesSelectedListener(
|
||||
selected -> {
|
||||
selectedAssignees = selected;
|
||||
updateAssigneesDisplay();
|
||||
updateAssigneesClearButtonVisibility();
|
||||
});
|
||||
assigneesPicker.show(getParentFragmentManager(), "ASSIGNEES_PICKER");
|
||||
}
|
||||
|
||||
private void updateAssigneesDisplay() {
|
||||
if (selectedAssignees.isEmpty()) {
|
||||
binding.cardAssignees.tvSelectedText.setText(R.string.add_assignees);
|
||||
} else {
|
||||
binding.cardAssignees.tvSelectedText.setText(String.join(", ", selectedAssignees));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAssigneesClearButtonVisibility() {
|
||||
binding.cardAssignees.btnClear.setVisibility(
|
||||
selectedAssignees.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void openReviewersPicker() {
|
||||
String currentUser = getCurrentUserName();
|
||||
|
||||
BottomSheetAssigneesPicker reviewersPicker =
|
||||
BottomSheetAssigneesPicker.newInstance(
|
||||
repoContext, new ArrayList<>(selectedReviewers), currentUser);
|
||||
reviewersPicker.setOnAssigneesSelectedListener(
|
||||
selected -> {
|
||||
selectedReviewers = selected;
|
||||
updateReviewersDisplay();
|
||||
updateReviewersClearButtonVisibility();
|
||||
});
|
||||
reviewersPicker.show(getParentFragmentManager(), "REVIEWERS_PICKER");
|
||||
}
|
||||
|
||||
private String getCurrentUserName() {
|
||||
UserAccountsApi userAccountsApi =
|
||||
BaseApi.getInstance(requireContext(), UserAccountsApi.class);
|
||||
if (userAccountsApi != null) {
|
||||
int currentAccountId = tinyDB.getInt("currentActiveAccountId", -1);
|
||||
UserAccount currentAccount = userAccountsApi.getAccountById(currentAccountId);
|
||||
if (currentAccount != null) {
|
||||
return currentAccount.getUserName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateReviewersDisplay() {
|
||||
if (selectedReviewers.isEmpty()) {
|
||||
binding.cardReviewers.tvSelectedText.setText(R.string.add_reviewers);
|
||||
} else {
|
||||
binding.cardReviewers.tvSelectedText.setText(String.join(", ", selectedReviewers));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateReviewersClearButtonVisibility() {
|
||||
binding.cardReviewers.btnClear.setVisibility(
|
||||
selectedReviewers.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void openDatePicker() {
|
||||
MaterialDatePicker<Long> datePicker = getLongMaterialDatePicker();
|
||||
datePicker.addOnPositiveButtonClickListener(
|
||||
selection -> {
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
calendar.setTimeInMillis(selection);
|
||||
String[] locale_ =
|
||||
AppDatabaseSettings.getSettingsValue(
|
||||
requireContext(), AppDatabaseSettings.APP_LOCALE_KEY)
|
||||
.split("\\|");
|
||||
SimpleDateFormat format =
|
||||
new SimpleDateFormat("yyyy-MM-dd", new Locale(locale_[1]));
|
||||
selectedDueDate = format.format(calendar.getTime());
|
||||
updateDueDateDisplay();
|
||||
updateDueDateClearButtonVisibility();
|
||||
});
|
||||
datePicker.show(getParentFragmentManager(), "DATE_PICKER");
|
||||
}
|
||||
|
||||
@NonNull private MaterialDatePicker<Long> getLongMaterialDatePicker() {
|
||||
MaterialDatePicker.Builder<Long> builder = MaterialDatePicker.Builder.datePicker();
|
||||
builder.setTitleText(R.string.newIssueDueDateTitle);
|
||||
if (selectedDueDate != null) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
Date date = sdf.parse(selectedDueDate);
|
||||
if (date != null) {
|
||||
builder.setSelection(date.getTime());
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
builder.setSelection(Calendar.getInstance().getTimeInMillis());
|
||||
}
|
||||
} else {
|
||||
builder.setSelection(Calendar.getInstance().getTimeInMillis());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void updateDueDateDisplay() {
|
||||
if (selectedDueDate == null || selectedDueDate.isEmpty()) {
|
||||
binding.cardDueDate.tvSelectedText.setText(R.string.add_due_date);
|
||||
} else {
|
||||
binding.cardDueDate.tvSelectedText.setText(formatDateForDisplay(selectedDueDate));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDueDateClearButtonVisibility() {
|
||||
binding.cardDueDate.btnClear.setVisibility(
|
||||
selectedDueDate == null || selectedDueDate.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void loadAttachmentLimits() {
|
||||
UserAccountsApi userAccountsApi =
|
||||
BaseApi.getInstance(requireContext(), UserAccountsApi.class);
|
||||
if (userAccountsApi != null) {
|
||||
UserAccount userAccount =
|
||||
userAccountsApi.getAccountById(tinyDB.getInt("currentActiveAccountId", -1));
|
||||
if (userAccount != null) {
|
||||
maxAttachmentSize = userAccount.getMaxAttachmentsSize();
|
||||
maxNumberOfAttachments = userAccount.getMaxNumberOfAttachments();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupAttachments() {
|
||||
loadAttachmentLimits();
|
||||
|
||||
if (maxNumberOfAttachments == 0) {
|
||||
binding.cardAttachments.getRoot().setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
binding.cardAttachments.getRoot().setVisibility(View.VISIBLE);
|
||||
attachmentManager = new AttachmentManager(requireContext());
|
||||
|
||||
if (maxAttachmentSize > 0) {
|
||||
attachmentManager.setMaxFileSize((long) maxAttachmentSize * 1024 * 1024);
|
||||
}
|
||||
if (maxNumberOfAttachments > 0) {
|
||||
attachmentManager.setMaxFileCount(maxNumberOfAttachments);
|
||||
}
|
||||
|
||||
attachmentManager.setListener(
|
||||
new AttachmentManager.AttachmentListener() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onAttachmentsChanged(int count) {
|
||||
binding.cardAttachments.attachmentCount.setText("(" + count + ")");
|
||||
updateAttachmentsEmptyState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentAdded(Uri uri) {
|
||||
attachmentsViewModel.addPendingUpload(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentRemoved(int position) {
|
||||
attachmentsViewModel.clearPendingUploads();
|
||||
for (Uri uri : attachmentManager.getPendingUris()) {
|
||||
attachmentsViewModel.addPendingUpload(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentRejected(String reason) {
|
||||
Toasty.show(requireContext(), reason);
|
||||
}
|
||||
});
|
||||
|
||||
ActivityResultLauncher<Intent> filePickerLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK
|
||||
&& result.getData() != null) {
|
||||
Uri uri = result.getData().getData();
|
||||
if (uri != null) {
|
||||
attachmentManager.handleFilePickerResult(uri);
|
||||
}
|
||||
ClipData clipData = result.getData().getClipData();
|
||||
if (clipData != null) {
|
||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||
Uri clipUri = clipData.getItemAt(i).getUri();
|
||||
if (clipUri != null) {
|
||||
attachmentManager.handleFilePickerResult(clipUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
attachmentManager.registerFilePicker(filePickerLauncher);
|
||||
|
||||
CreateAttachmentsAdapter attachmentsAdapter = attachmentManager.createAdapter();
|
||||
binding.cardAttachments.attachmentsRecyclerView.setLayoutManager(
|
||||
new LinearLayoutManager(requireContext()));
|
||||
binding.cardAttachments.attachmentsRecyclerView.setAdapter(attachmentsAdapter);
|
||||
|
||||
updateAttachmentsEmptyState();
|
||||
}
|
||||
|
||||
private void updateAttachmentsEmptyState() {
|
||||
boolean hasAttachments =
|
||||
attachmentManager != null && attachmentManager.getAttachmentCount() > 0;
|
||||
binding.cardAttachments.attachmentEmptyState.setVisibility(
|
||||
hasAttachments ? View.GONE : View.VISIBLE);
|
||||
binding.cardAttachments.attachmentsRecyclerView.setVisibility(
|
||||
hasAttachments ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void observeAttachmentsViewModel() {
|
||||
attachmentsViewModel
|
||||
.getIsUploading()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
isUploading -> {
|
||||
if (!isUploading) {
|
||||
binding.btnSubmit.setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
attachmentsViewModel
|
||||
.getUploadError()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
Toasty.show(requireContext(), R.string.attachmentsSaveError);
|
||||
attachmentsViewModel.clearError();
|
||||
}
|
||||
});
|
||||
|
||||
attachmentsViewModel
|
||||
.getUploadComplete()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
complete -> {
|
||||
if (complete != null && complete) {
|
||||
String successMsg =
|
||||
getString(
|
||||
prToEdit != null
|
||||
? R.string.updatePrSuccess
|
||||
: R.string.prCreateSuccess);
|
||||
Toasty.show(requireContext(), successMsg);
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePrSuccess(long prNumber) {
|
||||
if (attachmentManager != null && attachmentManager.getAttachmentCount() > 0) {
|
||||
attachmentsViewModel.uploadAttachments(
|
||||
requireContext(), repoContext.getOwner(), repoContext.getName(), prNumber);
|
||||
} else {
|
||||
String successMsg =
|
||||
getString(
|
||||
prToEdit != null ? R.string.updatePrSuccess : R.string.prCreateSuccess);
|
||||
Toasty.show(requireContext(), successMsg);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private String formatDateForDisplay(Date date) {
|
||||
if (date == null) return "";
|
||||
SimpleDateFormat displayFormat = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
|
||||
return displayFormat.format(date);
|
||||
}
|
||||
|
||||
private String formatDateForDisplay(String dateString) {
|
||||
if (dateString == null || dateString.isEmpty()) return "";
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
Date date = sdf.parse(dateString);
|
||||
return formatDateForDisplay(date);
|
||||
} catch (ParseException e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
private Date getDueDateForApi() {
|
||||
if (selectedDueDate == null || selectedDueDate.isEmpty()) return null;
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
return sdf.parse(selectedDueDate);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog instanceof BottomSheetDialog) {
|
||||
AppUtil.applyFullScreenSheetStyle((BottomSheetDialog) dialog, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
if (attachmentManager != null) {
|
||||
attachmentManager.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.gitnex.tea4j.v2.models.PullRequest;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.activities.CreatePullRequestActivity;
|
||||
import org.mian.gitnex.activities.RepoDetailActivity;
|
||||
import org.mian.gitnex.adapters.PullRequestsAdapter;
|
||||
import org.mian.gitnex.databinding.FragmentPullRequestsBinding;
|
||||
@@ -130,8 +129,8 @@ public class PullRequestsFragment extends Fragment implements RepoDetailActivity
|
||||
break;
|
||||
|
||||
case "PR_CREATE_NEW":
|
||||
startActivity(
|
||||
repository.getIntent(requireContext(), CreatePullRequestActivity.class));
|
||||
BottomSheetCreatePullRequest.newInstance(repository, null)
|
||||
.show(getParentFragmentManager(), "CREATE_PULL_REQUEST");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -281,6 +280,22 @@ public class PullRequestsFragment extends Fragment implements RepoDetailActivity
|
||||
binding.pullToRefresh.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
|
||||
viewModel
|
||||
.getActionResult()
|
||||
.observe(
|
||||
getViewLifecycleOwner(),
|
||||
code -> {
|
||||
if (code == null || code == -1) return;
|
||||
|
||||
if (code == 200 || code == 201) {
|
||||
refreshData();
|
||||
} else {
|
||||
Toasty.show(requireContext(), R.string.genericError);
|
||||
}
|
||||
|
||||
viewModel.resetActionResult();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUiState() {
|
||||
|
||||
@@ -8,7 +8,10 @@ import androidx.lifecycle.ViewModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.gitnex.tea4j.v2.models.CreatePullRequestOption;
|
||||
import org.gitnex.tea4j.v2.models.EditPullRequestOption;
|
||||
import org.gitnex.tea4j.v2.models.PullRequest;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.clients.RetrofitClient;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
@@ -25,6 +28,13 @@ public class PullRequestsViewModel extends ViewModel {
|
||||
private final MutableLiveData<Boolean> hasLoadedOnce = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||
private final MutableLiveData<Integer> repoTotalPrCountLiveData = new MutableLiveData<>(-1);
|
||||
private final MutableLiveData<Boolean> isCreating = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<Boolean> isUpdating = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<PullRequest> createdPr = new MutableLiveData<>();
|
||||
private final MutableLiveData<PullRequest> updatedPr = new MutableLiveData<>();
|
||||
private final MutableLiveData<String> createError = new MutableLiveData<>();
|
||||
private final MutableLiveData<String> updateError = new MutableLiveData<>();
|
||||
private final MutableLiveData<Integer> actionResult = new MutableLiveData<>(-1);
|
||||
|
||||
private boolean isLastPage = false;
|
||||
private int totalCount = -1;
|
||||
@@ -45,10 +55,58 @@ public class PullRequestsViewModel extends ViewModel {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getIsCreating() {
|
||||
return isCreating;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getIsUpdating() {
|
||||
return isUpdating;
|
||||
}
|
||||
|
||||
public LiveData<PullRequest> getCreatedPr() {
|
||||
return createdPr;
|
||||
}
|
||||
|
||||
public LiveData<PullRequest> getUpdatedPr() {
|
||||
return updatedPr;
|
||||
}
|
||||
|
||||
public LiveData<String> getCreateError() {
|
||||
return createError;
|
||||
}
|
||||
|
||||
public LiveData<String> getUpdateError() {
|
||||
return updateError;
|
||||
}
|
||||
|
||||
public LiveData<Integer> getActionResult() {
|
||||
return actionResult;
|
||||
}
|
||||
|
||||
public LiveData<Integer> getRepoPrTotalCount() {
|
||||
return repoTotalPrCountLiveData;
|
||||
}
|
||||
|
||||
public void clearCreatedPr() {
|
||||
createdPr.setValue(null);
|
||||
}
|
||||
|
||||
public void clearUpdatedPr() {
|
||||
updatedPr.setValue(null);
|
||||
}
|
||||
|
||||
public void clearCreateError() {
|
||||
createError.setValue(null);
|
||||
}
|
||||
|
||||
public void clearUpdateError() {
|
||||
updateError.setValue(null);
|
||||
}
|
||||
|
||||
public void resetActionResult() {
|
||||
actionResult.setValue(-1);
|
||||
}
|
||||
|
||||
public void resetPagination() {
|
||||
isLastPage = false;
|
||||
totalCount = -1;
|
||||
@@ -132,4 +190,86 @@ public class PullRequestsViewModel extends ViewModel {
|
||||
public void prefetchPrCounts(Context ctx, String owner, String repo) {
|
||||
fetchPullRequests(ctx, owner, repo, "open", 1, 1, true);
|
||||
}
|
||||
|
||||
public void createPullRequest(
|
||||
Context ctx, String owner, String repo, CreatePullRequestOption prData) {
|
||||
isCreating.setValue(true);
|
||||
createError.setValue(null);
|
||||
|
||||
Call<PullRequest> call =
|
||||
RetrofitClient.getApiInterface(ctx).repoCreatePullRequest(owner, repo, prData);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<PullRequest> call,
|
||||
@NonNull Response<PullRequest> response) {
|
||||
isCreating.setValue(false);
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
createdPr.setValue(response.body());
|
||||
actionResult.setValue(201);
|
||||
} else {
|
||||
handleCreateUpdateError(response.code(), ctx, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<PullRequest> call, @NonNull Throwable t) {
|
||||
isCreating.setValue(false);
|
||||
createError.setValue(t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updatePullRequest(
|
||||
Context ctx, String owner, String repo, long prIndex, EditPullRequestOption prData) {
|
||||
isUpdating.setValue(true);
|
||||
updateError.setValue(null);
|
||||
|
||||
Call<PullRequest> call =
|
||||
RetrofitClient.getApiInterface(ctx)
|
||||
.repoEditPullRequest(owner, repo, prIndex, prData);
|
||||
|
||||
call.enqueue(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public void onResponse(
|
||||
@NonNull Call<PullRequest> call,
|
||||
@NonNull Response<PullRequest> response) {
|
||||
isUpdating.setValue(false);
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
updatedPr.setValue(response.body());
|
||||
actionResult.setValue(200);
|
||||
} else {
|
||||
handleCreateUpdateError(response.code(), ctx, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<PullRequest> call, @NonNull Throwable t) {
|
||||
isUpdating.setValue(false);
|
||||
updateError.setValue(t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleCreateUpdateError(int code, Context ctx, boolean isCreate) {
|
||||
String errorMsg =
|
||||
switch (code) {
|
||||
case 401 -> "UNAUTHORIZED";
|
||||
case 403 -> ctx.getString(R.string.authorizeError);
|
||||
case 404 -> ctx.getString(R.string.apiNotFound);
|
||||
case 409 -> ctx.getString(R.string.prAlreadyExists);
|
||||
default -> ctx.getString(R.string.genericError);
|
||||
};
|
||||
|
||||
if (isCreate) {
|
||||
createError.setValue(errorMsg);
|
||||
} else {
|
||||
updateError.setValue(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/primaryBackgroundColor"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/primaryBackgroundColor">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
android:background="?attr/primaryBackgroundColor"
|
||||
app:contentScrim="?attr/primaryBackgroundColor"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/topAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:elevation="0dp"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:title="@string/pageTitleNewPullRequest"
|
||||
app:layout_collapseMode="pin"
|
||||
app:menu="@menu/create_issue_menu"
|
||||
app:navigationIcon="@drawable/ic_close" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
style="@style/Widget.Material3.LinearProgressIndicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="?attr/progressIndicatorColor" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/dimen16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/prTitleLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/newIssueTitle"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:boxStrokeErrorColor="@color/darkRed"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="255"
|
||||
app:counterTextColor="?attr/inputTextColor"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/prTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textCapSentences"
|
||||
android:singleLine="true"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/insertNote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen0dp"
|
||||
android:text="@string/insertNote"
|
||||
android:textColor="?attr/primaryTextColor"
|
||||
android:textSize="@dimen/dimen14sp"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/prBodyLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/description"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:boxStrokeErrorColor="@color/darkRed"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/prBody"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dimen180dp"
|
||||
android:gravity="top|start"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:scrollbars="vertical"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/markdown_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/dimen186dp"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:textColor="?attr/primaryTextColor"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/dimen14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/mergeIntoBranchSpinnerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/mergeIntoBranch"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/mergeIntoBranchSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/pullFromBranchSpinnerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/pullFromBranch"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/pullFromBranchSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/milestonesSpinnerLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/milestone"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/milestonesSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:labelFor="@+id/milestonesSpinner"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/prLabelsLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/newIssueLabelsTitle"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/prLabels"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/prDueDateLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/newIssueDueDateTitle"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:boxStrokeErrorColor="@color/darkRed"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/prDueDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,183 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/primaryBackgroundColor"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/primaryBackgroundColor">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
android:background="?attr/primaryBackgroundColor"
|
||||
app:contentScrim="?attr/primaryBackgroundColor"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/topAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:elevation="0dp"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:title="@string/editIssueNavHeader"
|
||||
app:layout_collapseMode="pin"
|
||||
app:menu="@menu/create_issue_menu"
|
||||
app:navigationIcon="@drawable/ic_close" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/dimen16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/editIssueTitleLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/newIssueTitle"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:boxStrokeErrorColor="@color/darkRed"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editIssueTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textCapSentences"
|
||||
android:singleLine="true"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/editIssueDescriptionLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/description"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:boxStrokeErrorColor="@color/darkRed"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editIssueDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dimen180dp"
|
||||
android:gravity="top|start"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:scrollbars="vertical"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/markdown_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/dimen186dp"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:textColor="?attr/primaryTextColor"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/dimen14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/editIssueMilestoneSpinnerLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/milestone"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/editIssueMilestoneSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:labelFor="@+id/editIssueMilestoneSpinner"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/editIssueDueDateLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen8dp"
|
||||
android:layout_marginBottom="@dimen/dimen8dp"
|
||||
android:hint="@string/newIssueDueDateTitle"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
app:boxStrokeErrorColor="@color/darkRed"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:hintTextColor="?attr/hintColor">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editIssueDueDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/inputTextColor"
|
||||
android:textColorHint="?attr/hintColor"
|
||||
android:textSize="@dimen/dimen16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/attachmentFrame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/attachmentsView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -4,12 +4,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
@@ -17,7 +11,7 @@
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
android:paddingTop="@dimen/dimen8dp"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sheet_title"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
android:text="@string/newIssueAssigneesListTitle"
|
||||
android:text="@string/pageTitleUsers"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -5,18 +5,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sheet_title"
|
||||
|
||||
216
app/src/main/res/layout/bottomsheet_create_pull_request.xml
Normal file
216
app/src/main/res/layout/bottomsheet_create_pull_request.xml
Normal file
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sheet_title"
|
||||
style="@style/TextAppearance.Material3.HeadlineSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_pr"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_close"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_close"
|
||||
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/close"
|
||||
app:icon="@drawable/ic_close"
|
||||
app:layout_constraintBottom_toBottomOf="@id/sheet_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/sheet_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/dimen16dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
android:paddingBottom="@dimen/dimen32dp"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintTop_toBottomOf="@id/header_layout"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/form_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/prTitleLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen12dp"
|
||||
android:hint="@string/title"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="255"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/prTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/description_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen12dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/prBodyLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/description"
|
||||
app:endIconMode="clear_text"
|
||||
app:endIconTint="?attr/iconsColor"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/prBody"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dimen224dp"
|
||||
android:gravity="top"
|
||||
android:scrollbars="vertical"
|
||||
android:paddingTop="@dimen/dimen40dp"
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_expand"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen6dp"
|
||||
android:layout_marginEnd="@dimen/dimen8dp"
|
||||
android:text="@string/fullscreen"
|
||||
app:icon="@drawable/ic_maximize"
|
||||
app:iconSize="@dimen/dimen20dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/card_merge_into"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_pull_from"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_milestone"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_labels"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_assignees"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_reviewers"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_due_date"
|
||||
layout="@layout/item_metadata_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_attachments"
|
||||
layout="@layout/item_metadata_attachment_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_allow_maintainer_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen16dp"
|
||||
android:checked="true"
|
||||
android:visibility="gone"
|
||||
android:text="@string/allow_maintainer_edit" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/dimen32dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_submit"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dimen56dp"
|
||||
android:text="@string/create_pr"
|
||||
app:cornerRadius="@dimen/dimen32dp" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/loading_indicator"
|
||||
android:layout_width="@dimen/dimen24dp"
|
||||
android:layout_height="@dimen/dimen24dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorSize="@dimen/dimen24dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -5,18 +5,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sheet_title"
|
||||
|
||||
@@ -4,18 +4,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sheet_title"
|
||||
@@ -44,7 +38,7 @@
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/dimen16dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
|
||||
@@ -5,18 +5,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dimen24dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sheet_title"
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/dimen12dp">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/pullToRefresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.loadingindicator.LoadingIndicator
|
||||
android:id="@+id/expressiveLoader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:indicatorSize="@dimen/dimen48dp"
|
||||
app:trackThickness="@dimen/dimen4dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layoutEmpty"
|
||||
layout="@layout/layout_empty_state"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -36,7 +36,7 @@
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="@dimen/dimen6dp" />
|
||||
android:paddingHorizontal="@dimen/dimen8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/save"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/saveButton"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
</menu>
|
||||
@@ -480,6 +480,7 @@
|
||||
<string name="editRelease">Edit Release</string>
|
||||
<string name="selectBranchError">Select a branch for release/tag</string>
|
||||
<string name="add_release_branch">Tap to select a branch</string>
|
||||
<string name="add_reviewers">Tap to select reviewers</string>
|
||||
<string name="create_release_tag">Create Release/Tag</string>
|
||||
<!-- release -->
|
||||
|
||||
@@ -661,6 +662,7 @@
|
||||
<string name="collapse">Collapse</string>
|
||||
<string name="fullscreen">Fullscreen</string>
|
||||
<string name="type">Type</string>
|
||||
<string name="reviewers">Reviewers</string>
|
||||
<!-- generic copy -->
|
||||
|
||||
<string name="exploreUsers">Explore users</string>
|
||||
@@ -708,6 +710,8 @@
|
||||
<string name="mergeNotAllowed">Not allowed to merge [Reason: Does not have enough approvals]</string>
|
||||
<string name="deleteBranch">Delete Branch</string>
|
||||
<string name="switch_branch">Switch Branch</string>
|
||||
<string name="allow_maintainer_edit">Allow maintainers to edit this pr</string>
|
||||
<string name="edit_pr">Edit Pull Request</string>
|
||||
|
||||
<string name="waitLoadingDownloadFile">Please wait for the file to load to memory</string>
|
||||
<string name="downloadFileSaved">File saved successfully</string>
|
||||
@@ -1170,7 +1174,7 @@
|
||||
<string name="labelColorHex">Hex Color</string>
|
||||
<string name="invalid_color">Invalid color code</string>
|
||||
<string name="invalid_length">Must be 6 characters</string>
|
||||
<string name="create_pr">Create PR</string>
|
||||
<string name="create_pr">Create Pull Request</string>
|
||||
<string name="search_filter">Search & filter</string>
|
||||
<string name="create_issue">Create Issue</string>
|
||||
<string name="search_repos">Search Repositories</string>
|
||||
@@ -1184,7 +1188,7 @@
|
||||
<string name="no_attachments">No attachments</string>
|
||||
<string name="attachment_limit_count">Maximum %d files allowed</string>
|
||||
<string name="attachment_limit_size">File exceeds %s limit</string>
|
||||
<string name="attachment_limit_total_size">Total size would exceed %s</string>
|
||||
<string name="cannotAddSelfAsReviewer">You cannot add yourself as a reviewer</string>
|
||||
|
||||
<string name="time_in_moments">in moments</string>
|
||||
<string name="time_in_minute">in %d minute</string>
|
||||
|
||||
Reference in New Issue
Block a user