From b493dfc567d1529b4f37c1dc29d35f7582e5b006 Mon Sep 17 00:00:00 2001 From: M M Arif Date: Tue, 28 Sep 2021 11:10:27 +0200 Subject: [PATCH] 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 Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/980 Reviewed-by: qwerty287 Co-authored-by: M M Arif Co-committed-by: M M Arif --- app/build.gradle | 2 +- .../AddCollaboratorToRepositoryActivity.java | 2 +- .../activities/AddNewTeamMemberActivity.java | 2 +- .../gitnex/activities/RepoDetailActivity.java | 101 +++++++++++++++-- .../BottomSheetIssuesFilterFragment.java | 11 ++ .../mian/gitnex/fragments/IssuesFragment.java | 107 ++++++++++-------- .../res/layout/bottom_sheet_issues_filter.xml | 16 +++ .../res/layout/custom_progress_loader.xml | 17 +++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 197 insertions(+), 62 deletions(-) create mode 100644 app/src/main/res/layout/custom_progress_loader.xml diff --git a/app/build.gradle b/app/build.gradle index 6e4210e2..ce12f266 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/org/mian/gitnex/activities/AddCollaboratorToRepositoryActivity.java b/app/src/main/java/org/mian/gitnex/activities/AddCollaboratorToRepositoryActivity.java index 546ae5bf..1769f6a8 100644 --- a/app/src/main/java/org/mian/gitnex/activities/AddCollaboratorToRepositoryActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/AddCollaboratorToRepositoryActivity.java @@ -81,7 +81,7 @@ public class AddCollaboratorToRepositoryActivity extends BaseActivity { Call call = RetrofitClient .getApiInterface(appCtx) - .getUserBySearch(Authorization.get(ctx), searchKeyword, 10); + .getUserBySearch(Authorization.get(ctx), searchKeyword, 10, 1); call.enqueue(new Callback() { diff --git a/app/src/main/java/org/mian/gitnex/activities/AddNewTeamMemberActivity.java b/app/src/main/java/org/mian/gitnex/activities/AddNewTeamMemberActivity.java index c34bde9f..43905572 100644 --- a/app/src/main/java/org/mian/gitnex/activities/AddNewTeamMemberActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/AddNewTeamMemberActivity.java @@ -110,7 +110,7 @@ public class AddNewTeamMemberActivity extends BaseActivity { public void loadUserSearchList(String searchKeyword, String teamId) { - Call call = RetrofitClient.getApiInterface(ctx).getUserBySearch(Authorization.get(ctx), searchKeyword, 10); + Call call = RetrofitClient.getApiInterface(ctx).getUserBySearch(Authorization.get(ctx), searchKeyword, 10, 1); mProgressBar.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java index 6e611788..bd70e623 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java @@ -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> call = RetrofitClient + .getApiInterface(ctx) + .getMilestones(Authorization.get(ctx), repositoryOwner, repositoryName, 1, 50, "open"); + + call.enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + progressDialog.hide(); + if(response.code() == 200) { + + Milestones milestones; + List 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> 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> 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> call, @NonNull Response> response) { + progressDialog.hide(); if(response.code() == 200) { List 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> 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; } diff --git a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetIssuesFilterFragment.java b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetIssuesFilterFragment.java index af941435..ccb8bbd0 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/BottomSheetIssuesFilterFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/BottomSheetIssuesFilterFragment.java @@ -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"); diff --git a/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java b/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java index eda404ac..2808760a 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java @@ -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 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> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, 1, resultLimit, requestType, issueState); + Call> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, 1, resultLimit, requestType, issueState, filterByMilestone); call.enqueue(new Callback>() { @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> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, page, resultLimit, requestType, issueState); + Call> call = RetrofitClient.getApiInterface(context).getIssues(token, repoOwner, repoName, page, resultLimit, requestType, issueState, filterByMilestone); call.enqueue(new Callback>() { @@ -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())); diff --git a/app/src/main/res/layout/bottom_sheet_issues_filter.xml b/app/src/main/res/layout/bottom_sheet_issues_filter.xml index 81f9b081..b0704f49 100644 --- a/app/src/main/res/layout/bottom_sheet_issues_filter.xml +++ b/app/src/main/res/layout/bottom_sheet_issues_filter.xml @@ -18,6 +18,22 @@ android:orientation="vertical" android:layout_height="wrap_content"> + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52bf35d1..a73f9a13 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -203,6 +203,7 @@ No description %1$d Open %1$d Closed + Select Milestone Select Assignees Select Labels