Filter issues by milestone (#980)

Closes #693

Right now I like how the API(filters) is for issues, for PRs it is only accepting Ids. It should be more like issues which has optional titles and more forgiving.

Maybe in the future will implement for PRs when the API become updated.

Issues:
https://codeberg.org/api/swagger#/issue/issueListIssues

PR:
https://codeberg.org/api/swagger#/repository/repoListPullRequests

Co-authored-by: M M Arif <mmarif@swatian.com>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/980
Reviewed-by: qwerty287 <qwerty287@noreply.codeberg.org>
Co-authored-by: M M Arif <mmarif@noreply.codeberg.org>
Co-committed-by: M M Arif <mmarif@noreply.codeberg.org>
This commit is contained in:
M M Arif 2021-09-28 11:10:27 +02:00
parent 95aea16a07
commit b493dfc567
9 changed files with 197 additions and 62 deletions

View File

@ -109,7 +109,7 @@ dependencies {
implementation "androidx.work:work-runtime:$work_version"
implementation "io.mikael:urlbuilder:2.0.9"
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
implementation "org.codeberg.gitnex:tea4j:1.0.20"
implementation "org.codeberg.gitnex:tea4j:1.0.24"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'com.github.chrisvest:stormpot:2.4.2'

View File

@ -81,7 +81,7 @@ public class AddCollaboratorToRepositoryActivity extends BaseActivity {
Call<UserSearch> call = RetrofitClient
.getApiInterface(appCtx)
.getUserBySearch(Authorization.get(ctx), searchKeyword, 10);
.getUserBySearch(Authorization.get(ctx), searchKeyword, 10, 1);
call.enqueue(new Callback<UserSearch>() {

View File

@ -110,7 +110,7 @@ public class AddNewTeamMemberActivity extends BaseActivity {
public void loadUserSearchList(String searchKeyword, String teamId) {
Call<UserSearch> call = RetrofitClient.getApiInterface(ctx).getUserBySearch(Authorization.get(ctx), searchKeyword, 10);
Call<UserSearch> call = RetrofitClient.getApiInterface(ctx).getUserBySearch(Authorization.get(ctx), searchKeyword, 10, 1);
mProgressBar.setVisibility(View.VISIBLE);

View File

@ -1,10 +1,10 @@
package org.mian.gitnex.activities;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
@ -29,6 +29,7 @@ import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import com.google.gson.JsonElement;
import org.gitnex.tea4j.models.Branches;
import org.gitnex.tea4j.models.Milestones;
import org.gitnex.tea4j.models.UserRepositories;
import org.gitnex.tea4j.models.WatchInfo;
import org.mian.gitnex.R;
@ -70,6 +71,7 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
private FragmentRefreshListenerPr fragmentRefreshListenerPr;
private FragmentRefreshListenerMilestone fragmentRefreshListenerMilestone;
private FragmentRefreshListenerFiles fragmentRefreshListenerFiles;
private FragmentRefreshListenerFilterIssuesByMilestone fragmentRefreshListenerFilterIssuesByMilestone;
private String repositoryOwner;
private String repositoryName;
@ -441,6 +443,9 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
startActivity(new Intent(RepoDetailActivity.this, CreateFileActivity.class));
break;
case "filterByMilestone":
filterIssuesByMilestone();
break;
case "openIssues":
if(getFragmentRefreshListener() != null) {
@ -494,8 +499,79 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
}
}
private void filterIssuesByMilestone() {
Dialog progressDialog = new Dialog(this);
progressDialog.setContentView(R.layout.custom_progress_loader);
progressDialog.show();
Call<List<Milestones>> call = RetrofitClient
.getApiInterface(ctx)
.getMilestones(Authorization.get(ctx), repositoryOwner, repositoryName, 1, 50, "open");
call.enqueue(new Callback<List<Milestones>>() {
@Override
public void onResponse(@NonNull Call<List<Milestones>> call, @NonNull Response<List<Milestones>> response) {
progressDialog.hide();
if(response.code() == 200) {
Milestones milestones;
List<String> milestonesList = new ArrayList<>();
int selectedMilestone = 0;
assert response.body() != null;
milestonesList.add("All");
for(int i = 0; i < response.body().size(); i++) {
milestones = response.body().get(i);
milestonesList.add(milestones.getTitle());
}
for(int j = 0; j < milestonesList.size(); j++) {
if(tinyDB.getString("issueMilestoneFilterId").equals(milestonesList.get(j))) {
selectedMilestone = j;
}
}
AlertDialog.Builder pBuilder = new AlertDialog.Builder(ctx);
pBuilder.setTitle(R.string.selectMilestone);
pBuilder.setSingleChoiceItems(milestonesList.toArray(new String[0]), selectedMilestone, (dialogInterface, i) -> {
tinyDB.putString("issueMilestoneFilterId", milestonesList.get(i));
if(getFragmentRefreshListenerFilterIssuesByMilestone() != null) {
getFragmentRefreshListenerFilterIssuesByMilestone().onRefresh(milestonesList.get(i));
}
dialogInterface.dismiss();
});
pBuilder.setNeutralButton(R.string.cancelButton, null);
pBuilder.create().show();
}
}
@Override
public void onFailure(@NonNull Call<List<Milestones>> call, @NonNull Throwable t) {
progressDialog.hide();
Log.e("onFailure", t.toString());
}
});
}
private void chooseBranch() {
Dialog progressDialog = new Dialog(this);
progressDialog.setContentView(R.layout.custom_progress_loader);
progressDialog.show();
Call<List<Branches>> call = RetrofitClient
.getApiInterface(ctx)
.getBranches(Authorization.get(ctx), repositoryOwner, repositoryName);
@ -505,6 +581,7 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
@Override
public void onResponse(@NonNull Call<List<Branches>> call, @NonNull Response<List<Branches>> response) {
progressDialog.hide();
if(response.code() == 200) {
List<String> branchesList = new ArrayList<>();
@ -525,19 +602,15 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
AlertDialog.Builder pBuilder = new AlertDialog.Builder(ctx);
pBuilder.setTitle(R.string.pageTitleChooseBranch);
pBuilder.setSingleChoiceItems(branchesList.toArray(new String[0]), selectedBranch, new DialogInterface.OnClickListener() {
pBuilder.setSingleChoiceItems(branchesList.toArray(new String[0]), selectedBranch, (dialogInterface, i) -> {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
tinyDB.putString("repoBranch", branchesList.get(i));
tinyDB.putString("repoBranch", branchesList.get(i));
if(getFragmentRefreshListenerFiles() != null) {
if(getFragmentRefreshListenerFiles() != null) {
getFragmentRefreshListenerFiles().onRefresh(branchesList.get(i));
}
dialogInterface.dismiss();
getFragmentRefreshListenerFiles().onRefresh(branchesList.get(i));
}
dialogInterface.dismiss();
});
pBuilder.setNeutralButton(R.string.cancelButton, null);
@ -548,6 +621,7 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
@Override
public void onFailure(@NonNull Call<List<Branches>> call, @NonNull Throwable t) {
progressDialog.hide();
Log.e("onFailure", t.toString());
}
});
@ -715,6 +789,13 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF
}
// Issues milestone filter interface
public FragmentRefreshListenerFilterIssuesByMilestone getFragmentRefreshListenerFilterIssuesByMilestone() { return fragmentRefreshListenerFilterIssuesByMilestone; }
public void setFragmentRefreshListenerFilterIssuesByMilestone(FragmentRefreshListenerFilterIssuesByMilestone fragmentRefreshListener) { this.fragmentRefreshListenerFilterIssuesByMilestone = fragmentRefreshListener; }
public interface FragmentRefreshListenerFilterIssuesByMilestone { void onRefresh(String text); }
// Issues interface
public FragmentRefreshListener getFragmentRefreshListener() { return fragmentRefreshListener; }

View File

@ -9,6 +9,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.databinding.BottomSheetIssuesFilterBinding;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Version;
/**
* Author M M Arif
@ -23,6 +25,15 @@ public class BottomSheetIssuesFilterFragment extends BottomSheetDialogFragment {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
BottomSheetIssuesFilterBinding bottomSheetIssuesFilterBinding = BottomSheetIssuesFilterBinding.inflate(inflater, container, false);
TinyDB tinyDb = TinyDB.getInstance(getContext());
if(new Version(tinyDb.getString("giteaVersion")).higherOrEqual("1.14.0")) {
bottomSheetIssuesFilterBinding.filterByMilestone.setVisibility(View.VISIBLE);
bottomSheetIssuesFilterBinding.filterByMilestone.setOnClickListener(v1 -> {
bmListener.onButtonClicked("filterByMilestone");
dismiss();
});
}
bottomSheetIssuesFilterBinding.openIssues.setOnClickListener(v1 -> {
bmListener.onButtonClicked("openIssues");

View File

@ -12,15 +12,11 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.gitnex.tea4j.models.Issues;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.RepoDetailActivity;
@ -45,18 +41,16 @@ import retrofit2.Response;
public class IssuesFragment extends Fragment {
private FragmentIssuesBinding fragmentIssuesBinding;
private Context context;
private Menu menu;
private RecyclerView recyclerView;
private List<Issues> issuesList;
private IssuesAdapter adapter;
private Context context;
private int pageSize = Constants.issuesPageInit;
private ProgressBar mProgressBar;
private final String TAG = Constants.tagIssuesList;
private TextView noDataIssues;
private int resultLimit = Constants.resultLimitOldGiteaInstances;
private final String requestType = Constants.issuesRequestType;
private ProgressBar progressLoadMore;
@Nullable
@Override
@ -66,7 +60,7 @@ public class IssuesFragment extends Fragment {
setHasOptionsMenu(true);
context = getContext();
TinyDB tinyDb = TinyDB.getInstance(getContext());
TinyDB tinyDb = TinyDB.getInstance(context);
String repoFullName = tinyDb.getString("repoFullName");
String[] parts = repoFullName.split("/");
final String repoOwner = parts[0];
@ -74,39 +68,32 @@ public class IssuesFragment extends Fragment {
final String loginUid = tinyDb.getString("loginUid");
final String instanceToken = "token " + tinyDb.getString(loginUid + "-token");
final SwipeRefreshLayout swipeRefresh = fragmentIssuesBinding.pullToRefresh;
// if gitea is 1.12 or higher use the new limit
if(new Version(tinyDb.getString("giteaVersion")).higherOrEqual("1.12.0")) {
resultLimit = Constants.resultLimitNewGiteaInstances;
}
recyclerView = fragmentIssuesBinding.recyclerView;
issuesList = new ArrayList<>();
progressLoadMore = fragmentIssuesBinding.progressLoadMore;
mProgressBar = fragmentIssuesBinding.progressBar;
noDataIssues = fragmentIssuesBinding.noDataIssues;
swipeRefresh.setOnRefreshListener(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> {
swipeRefresh.setRefreshing(false);
loadInitial(instanceToken, repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"));
fragmentIssuesBinding.pullToRefresh.setOnRefreshListener(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> {
fragmentIssuesBinding.pullToRefresh.setRefreshing(false);
loadInitial(instanceToken, repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"), "");
adapter.notifyDataChanged();
}, 200));
adapter = new IssuesAdapter(getContext(), issuesList);
adapter.setLoadMoreListener(() -> recyclerView.post(() -> {
adapter = new IssuesAdapter(context, issuesList);
adapter.setLoadMoreListener(() -> fragmentIssuesBinding.recyclerView.post(() -> {
if(issuesList.size() == resultLimit || pageSize == resultLimit) {
int page = (issuesList.size() + resultLimit) / resultLimit;
loadMore(Authorization.get(getContext()), repoOwner, repoName, page, resultLimit, requestType, tinyDb.getString("repoIssuesState"));
loadMore(Authorization.get(context), repoOwner, repoName, page, resultLimit, requestType, tinyDb.getString("repoIssuesState"), "");
}
}));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(dividerItemDecoration);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.setAdapter(adapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(fragmentIssuesBinding.recyclerView.getContext(), DividerItemDecoration.VERTICAL);
fragmentIssuesBinding.recyclerView.setHasFixedSize(true);
fragmentIssuesBinding.recyclerView.addItemDecoration(dividerItemDecoration);
fragmentIssuesBinding.recyclerView.setLayoutManager(new LinearLayoutManager(context));
fragmentIssuesBinding.recyclerView.setAdapter(adapter);
((RepoDetailActivity) requireActivity()).setFragmentRefreshListener(issueState -> {
@ -119,25 +106,47 @@ public class IssuesFragment extends Fragment {
issuesList.clear();
adapter = new IssuesAdapter(getContext(), issuesList);
adapter.setLoadMoreListener(() -> recyclerView.post(() -> {
adapter = new IssuesAdapter(context, issuesList);
adapter.setLoadMoreListener(() -> fragmentIssuesBinding.recyclerView.post(() -> {
if(issuesList.size() == resultLimit || pageSize == resultLimit) {
int page = (issuesList.size() + resultLimit) / resultLimit;
loadMore(Authorization.get(getContext()), repoOwner, repoName, page, resultLimit, requestType, tinyDb.getString("repoIssuesState"));
loadMore(Authorization.get(context), repoOwner, repoName, page, resultLimit, requestType, tinyDb.getString("repoIssuesState"), "");
}
}));
tinyDb.putString("repoIssuesState", issueState);
mProgressBar.setVisibility(View.VISIBLE);
noDataIssues.setVisibility(View.GONE);
fragmentIssuesBinding.progressBar.setVisibility(View.VISIBLE);
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE);
loadInitial(Authorization.get(getContext()), repoOwner, repoName, resultLimit, requestType, issueState);
recyclerView.setAdapter(adapter);
loadInitial(Authorization.get(context), repoOwner, repoName, resultLimit, requestType, issueState, "");
fragmentIssuesBinding.recyclerView.setAdapter(adapter);
});
loadInitial(Authorization.get(getContext()), repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"));
((RepoDetailActivity) requireActivity()).setFragmentRefreshListenerFilterIssuesByMilestone(filterIssueByMilestone -> {
issuesList.clear();
adapter = new IssuesAdapter(context, issuesList);
adapter.setLoadMoreListener(() -> fragmentIssuesBinding.recyclerView.post(() -> {
if(issuesList.size() == resultLimit || pageSize == resultLimit) {
int page = (issuesList.size() + resultLimit) / resultLimit;
loadMore(Authorization.get(context), repoOwner, repoName, page, resultLimit, requestType, tinyDb.getString("repoIssuesState"), tinyDb.getString("issueMilestoneFilterId"));
}
}));
tinyDb.putString("issueMilestoneFilterId", filterIssueByMilestone);
fragmentIssuesBinding.progressBar.setVisibility(View.VISIBLE);
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE);
loadInitial(Authorization.get(context), repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"), tinyDb.getString("issueMilestoneFilterId"));
fragmentIssuesBinding.recyclerView.setAdapter(adapter);
});
loadInitial(Authorization.get(context), repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"), tinyDb.getString("issueMilestoneFilterId"));
return fragmentIssuesBinding.getRoot();
}
@ -146,7 +155,7 @@ public class IssuesFragment extends Fragment {
public void onResume() {
super.onResume();
TinyDB tinyDb = TinyDB.getInstance(getContext());
TinyDB tinyDb = TinyDB.getInstance(context);
String repoFullName = tinyDb.getString("repoFullName");
String[] parts = repoFullName.split("/");
@ -154,14 +163,14 @@ public class IssuesFragment extends Fragment {
final String repoName = parts[1];
if(tinyDb.getBoolean("resumeIssues")) {
loadInitial(Authorization.get(getContext()), repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"));
loadInitial(Authorization.get(context), repoOwner, repoName, resultLimit, requestType, tinyDb.getString("repoIssuesState"), tinyDb.getString("issueMilestoneFilterId"));
tinyDb.putBoolean("resumeIssues", false);
}
}
private void loadInitial(String token, String repoOwner, String repoName, int resultLimit, String requestType, String issueState) {
private void loadInitial(String token, String repoOwner, String repoName, int resultLimit, String requestType, String issueState, String filterByMilestone) {
Call<List<Issues>> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, 1, resultLimit, requestType, issueState);
Call<List<Issues>> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, 1, resultLimit, requestType, issueState, filterByMilestone);
call.enqueue(new Callback<List<Issues>>() {
@Override
@ -173,18 +182,18 @@ public class IssuesFragment extends Fragment {
issuesList.clear();
issuesList.addAll(response.body());
adapter.notifyDataChanged();
noDataIssues.setVisibility(View.GONE);
fragmentIssuesBinding.noDataIssues.setVisibility(View.GONE);
}
else {
issuesList.clear();
adapter.notifyDataChanged();
noDataIssues.setVisibility(View.VISIBLE);
fragmentIssuesBinding.noDataIssues.setVisibility(View.VISIBLE);
}
mProgressBar.setVisibility(View.GONE);
fragmentIssuesBinding.progressBar.setVisibility(View.GONE);
}
else if(response.code() == 404) {
noDataIssues.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
fragmentIssuesBinding.noDataIssues.setVisibility(View.VISIBLE);
fragmentIssuesBinding.progressBar.setVisibility(View.GONE);
}
else {
Log.e(TAG, String.valueOf(response.code()));
@ -198,11 +207,11 @@ public class IssuesFragment extends Fragment {
});
}
private void loadMore(String token, String repoOwner, String repoName, int page, int resultLimit, String requestType, String issueState) {
private void loadMore(String token, String repoOwner, String repoName, int page, int resultLimit, String requestType, String issueState, String filterByMilestone) {
progressLoadMore.setVisibility(View.VISIBLE);
fragmentIssuesBinding.progressLoadMore.setVisibility(View.VISIBLE);
Call<List<Issues>> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, page, resultLimit, requestType, issueState);
Call<List<Issues>> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, page, resultLimit, requestType, issueState, filterByMilestone);
call.enqueue(new Callback<List<Issues>>() {
@ -220,7 +229,7 @@ public class IssuesFragment extends Fragment {
adapter.setMoreDataAvailable(false);
}
adapter.notifyDataChanged();
progressLoadMore.setVisibility(View.GONE);
fragmentIssuesBinding.progressLoadMore.setVisibility(View.GONE);
}
else {
Log.e(TAG, String.valueOf(response.code()));

View File

@ -18,6 +18,22 @@
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:id="@+id/filterByMilestone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"
android:drawablePadding="24dp"
android:padding="12dp"
android:text="@string/newIssueMilestoneTitle"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_milestone" />
<TextView
android:id="@+id/openIssues"
android:layout_width="match_parent"

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:minWidth="360dp"
style="@style/Widget.MaterialComponents.LinearProgressIndicator"
app:indicatorColor="?attr/progressIndicatorColor" />
</LinearLayout>

View File

@ -203,6 +203,7 @@
<string name="milestoneNoDescription">No description</string>
<string name="milestoneIssueStatusOpen">%1$d Open</string>
<string name="milestoneIssueStatusClosed">%1$d Closed</string>
<string name="selectMilestone">Select Milestone</string>
<string name="newIssueSelectAssigneesListTitle">Select Assignees</string>
<string name="newIssueSelectLabelsListTitle">Select Labels</string>